Every content source (like your Contentstack, Contentful, Sanity CMS or Shopify blog) is different but the same principles apply when adding content aside of products to an eCommerce search:
The content source is accessed (e.g. “get all content items”, “notify me when a blog post is created” or “notify me when a page is updated”).
The content fields are cleaned and mapped to the search provider’s data structure.
The mapped data is sent to the index via an API of the search provider.
No-code and low-code tools like Zapier, Make or n8n enable non-developers to build automations within only a few minutes. You can follow this tutorial and have your first automated workflow set up within 30-60 minutes.
Prerequisites
Zapier, Make.com or n8n account (or any other tool)
Permission to create an API token for Contentstack
Your public blog URL, e.g. https://your-blog/article/{my-blog-post} (make sure this domain is white-listed in your Nosto account settings)
Minimal technical understanding
Nosto API_APPS token
You can create an account in minutes and request a Nosto API token from the Nosto team.
Zapier Workflow Templates
If you are on a Zapier “Team” or “Enterprise” account, you can import the workflows from our tutorials using the following .json files. Please make sure to go through the workflow and update all fields accordingly.
1. Retrieve your Contentstack API key for your stack
In case you need further information for authentication, please refer to the Contentstack docs: https://www.contentstack.com/docs/developers/create-tokens/about-delivery-tokens
Login to Contentstack, select your Stack and navigate to “Settings”.
Within the settings of your stack, navigate to “Tokens” and create a new “Delivery Token”.
Set a name like “Zapier Nosto”, select your branch (“main”) and publishing environment (“production”), then click “Generate Token.
Copy your “Delivery Token” and “Stack API Key” from the updated page - we need those later.
2. Determine the Content Type UID
You will likely have multiple types of content like pages or blog posts. For each type you will create a separate workflow to send the specific content entries to Nosto.
In your Stack, navigate to “Content Models” and click on the type you want to send to Nosto.
Click the pencil/edit icon to copy the UID from the popup or copy the UID from the URL.
3. Create the “Contentful: Blog Posts initial Sync to Nosto” Workflow
The following principle can vary depending on the tool you are using and can also be used for other content sources like Contentful, Sanity or Shopify.
If you have multiple Nosto accounts (e.g. because you are using the Shopify Markets integration), you will need to duplicate this workflow per language/Nosto account.
Create a new workflow (or “Zap” in Zapier terms) and call it “Contentstack: Blog Posts initial Sync to Nosto Product API”.
Select “Webhooks by Zapier” as trigger and set the “Trigger event” to “Catch Hook”.
3.1. Get the data from Contentstack
In case you need further information regarding the Contentstack API, please refer to the Contentstack documentation: https://www.contentstack.com/docs/developers/apis/content-delivery-api
Create a new action “Webhooks by Zapier”, set the “Action event” to “Custom Event” and choose “GET” as the method.
Set the URL to https://cdn.contentstack.io/v3/content_types/CONTENT_TYPE_UID/entries (your base URL might differ depending on your location, e.g.: https://eu-cdn.contentstack.com/v3/content_types/blog_post/entries for AWS EU and “blog_post” content type UID).
Replace CONTENT_TYPE_UID in the URL with your Content Type UID from step 2.
Add three new “Headers” fields and set the key-value pairs as follows (using the values from step 1):
api_key -> Your Stack API Key
access_token -> Your Delivery Token
environment -> Your Environment Name (e.g., production).
2.2. Prepare the data from Contentstack for Nosto
The Contentstack API will return your content in multiple fields, so you need to combine all the paragraphs into a single field. Depending on your content, this step may vary a bit.
Create a new action with the “Code by Zapier” app and set the “Action event” to “Run Python”.
Add a new field “response” under “Input Data” and select the output from the previous step.
Copy the following code into the “Code” field (you might want to expand the window in this step with the “expand” icon)
import json
import re
# --- Start of your main Zapier Code logic ---
# 1. Parse the entire JSON string response body from the Zapier input
# Assumes input is mapped to 'response'
data = json.loads(input_data['response'])
# Contentstack returns the list of documents under the 'entries' key
documents = data.get('entries', [])
output = []
for doc in documents:
# 2. Extract core fields from the Contentstack entry
# Contentstack uses 'uid' for the entry ID
doc_id = doc.get('uid')
title = doc.get('title', 'Untitled Entry')
# Contentstack provides a relative 'url' field for the entry's path
slug = doc.get('url')
# 3. Extract Image URL
# !! IMPORTANT: Change 'image' to your actual image field name
image_url = None
image_field = doc.get('featured_image') # e.g., 'main_image', 'featured_image'
# Check if the field exists, is an object, and has a 'url' key
if image_field and isinstance(image_field, dict) and 'url' in image_field:
image_url = image_field.get('url')
# 4. Concatenate the Contentstack Rich Text (RTE) Body
rte_field = doc.get('body') # <--- CHECK YOUR RTE FIELD NAME HERE
# Check if the body field is a dictionary (the full RTE object)
if not isinstance(rte_field, dict):
final_body = ""
# Skip if the body field is missing or not the expected RTE object
else:
# The main content array is inside the 'children' key of the 'doc' type
rte_blocks = rte_field.get('children', [])
concatenated_body = ""
# Helper function to extract text from a child node
def extract_text(node):
if isinstance(node, dict):
if 'text' in node:
return node['text']
if 'children' in node:
# Recursively process children
return "".join(extract_text(child) for child in node['children'])
return ""
for block in rte_blocks:
block_type = block.get('type')
# Process Paragraphs and Headings
if block_type in ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']:
# The text is inside the 'children' of the block
text = extract_text(block)
if text:
concatenated_body += text + " "
# Process Lists (Ordered or Unordered)
elif block_type in ['ol', 'ul']:
# Iterate through list items (li)
for list_item in block.get('children', []):
if list_item.get('type') == 'li':
text = extract_text(list_item)
# Add a bullet and the text
prefix = "* "
if text:
concatenated_body += prefix + text + " "
concatenated_body += " "
final_body = concatenated_body.strip().replace('\n', ' ') \
.replace('"', "'") \
.replace('“', "'") \
.replace('”', "'") \
.replace("‘", "'") \
.replace("’", "'")
# 5. Get SEO data
seo_data = doc.get('seo', [])
seo_keywords = ''
if isinstance(seo_data, dict):
if 'keywords' in seo_data:
seo_keywords = seo_data.get('keywords', '')
# 6. Create the new structured item
processed_item = {
"id": doc_id,
"title": title,
"slug": slug,
"image_url": image_url,
"concatenated_body": final_body,
"seo_keywords": seo_keywords
}
output.append(processed_item)
# Zapier expects a list of dictionaries for the output and will process each item in a single run
return output
Click the “Continue” button and verify the data has been processed correctly (e.g. the body only contains text, the image URL is valid and the slug is correct)
2.3. Send the clean data to Nosto
You could extend the code in the previous step and send the following GraphQL request via Python if you feel comfortable adding such a call to your code.
Create a new action “Webhooks by Zapier”, set the “Action event” to “Custom Request” and set the “Method” to “POST”. We need this step because Shopify doesn’t return the blog post URL in the trigger step.
Set the “URL” to: https://api.nosto.com/v1/graphql
Copy the following query into the “Data” field:
mutation {
updateProducts(products: [
{
productId: ""
url: ""
imageUrl: ""
priceCurrencyCode: "USD"
name: ""
attributes: [
{
key: "contentType"
value: "article"
},
{
key: "body"
value: ""
}
{
key: "keywords"
value: ""
}
]
price: 1
listPrice: 1
inventoryLevel:1
availability:"InStock"
}
]) {
result {
errors {
field
message
}
data {
productId
}
}
}
}One by one, click into all the empty strings (""), click the “+” icon and select the matching field from previous steps with the popup. You will likely “map” the fields similar to this (make sure to adjust your blog URL when adding the slug):
productId: “Step3.ID”
url: https://your-blog/article/“Step3.Slug”
imageUrl: “Step3.Image Url”
name: “Step3.Title”
attributes.body.value: “Step3.Concatenated Body”
attributes.keywords.value: “Step3.Seo Keywords”
Scroll down, add a new “Headers” field “Content-Type” and set the value to “application/graphql”
Copy your Nosto API_APPS token, prepend “:” and encode the string with base64 (e.g. via https://www.base64encode.org/ or any other service, example: base64_encode(“:k8yvSJBge6Jw”))
Add another new “Headers” field “Authorization” and set the value to “Basic <base64_encoded_Nosto_API_APPs_token>”
Click “Publish” and Nosto will receive all newly published blog posts within a few minutes.
If you are using Nosto variations for fixed price multicurrency or customer group pricing, you need to amend your updateProducts mutation to include the main variation and the individual variations, for example:
mutation {
updateProducts(products: [
{
productId: ""
"""
Other fields from the mutation above
"""
priceCurrencyCode: "USD"
price: 1
listPrice: 1
inventoryLevel: 1
availability: "InStock"
variation_id: "GENERAL"
variations: [
{
variant_id: "LOYAL"
},
{
variant_id: "WHOLESALE"
}
]
}
]) {
result {
errors {
field
message
}
data {
productId
}
}
}
}
3. Additional Use Cases and Resources
The workflow above can be created within one hour. You will likely want to copy and adjust certain elements of the main workflow to keep your data in Nosto up-to-date as you will update your content/blog posts over time.
As always, you have different options depending on what your automation tool and content sources like Shopify, Contentstack, Contentful or Sanity provide:
Zapier provides an “Publish Entry” and “Update Entry” trigger that you can use in the same way we are using the “Shopify: Blog Post created” trigger in this tutorial. Just copy the Shopify workflow, change the trigger, update the mapping and you’re good to go within minutes.
Make.com provides an “event” trigger that might be useful for updating your content.
If you’re using a CMS or blog system that doesn’t provide APIs/webhooks but has “feed”-files (like RSS, Atom or another XML format) your approach of getting the data will vary a bit but the principle remains the same: Read the data regularly, clean and map it, then send it to Nosto.


