Skip to main content

Sanity Integration - automatically send Content from Sanity CMS to Nosto for Content Search

How to automatically send Content from Sanity CMS to Nosto for Content Search

Julian Wittorf avatar
Written by Julian Wittorf
Updated this week

Every content source (like your Sanity, Contentful, Contentstack CMS or Shopify blog) is different but the same principles apply when adding content aside of products to an eCommerce search:

  1. 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”).

  2. The content fields are cleaned and mapped to the search provider’s data structure.

  3. 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)

  • Knowledge of your schemaTypes in Sanity

  • 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. Build a GROQ Query in Sanity Vision

  • Login to Sanity, click on the name of your content studio and select the “Vision” section.

  • Copy and paste the following query into the “QUERY” field.

*[_type == "blogPost"]{
_id,
title,
slug,
publishedAt,
"imageUrl": image.asset->url,
"imageFileName": image.asset->originalFilename,
body
}
  • Open your Sanity code base, look for the /schemaTypes directory and open the schema file of the content type you want to send to Nosto (blogPostType.ts in this example).

  • Adjust the query to match your schema, you might need to dereference some values to e.g. get the image URL.

  • Click the “Fetch” button, confirm your query returns a valid result and copy the query URL to your clipboard.

2. Create the “Sanity: 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 Contentstack 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 “Sanity: Blog Posts initial Sync to Nosto Product API”.

  • Select “Webhooks by Zapier” as trigger and set the “Trigger event” to “Catch Hook”.

2.1. Get the data from Sanity

In case you need further information regarding GROQ, please refer to the Sanity cheat sheet and documentation: https://www.sanity.io/docs/content-lake/query-cheat-sheet

  • If you want to access draft or private documents, you need to add a new “Headers” field “Authorization” and set the value to “Bearer SANITY_API_KEY”.

2.2. Prepare the data from Sanity for Nosto

The Sanity 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

# 1. Parse the entire JSON string response body from the Zapier input
data = json.loads(input_data['response'])

documents = data.get('result', [])
output = []

for doc in documents:
# 2. Extract core fields from the Sanity document
doc_id = doc.get('_id')
title = doc.get('title', 'Untitled Entry')

# Sanity returns slug as an object with a 'current' property
slug = doc.get('slug', 'untitled-entry').get('current')

image_url = doc.get('imageUrl', 'https://placehold.co/500') # Directly available from GROQ query

# 3. Concatenate the Portable Text Body (Sanity's Rich Text)
# The body is an array of 'block' objects.
portable_text_blocks = doc.get('body', [])
concatenated_body = ""

# Iterate through each 'block' in the body array
for block in portable_text_blocks:
# We only want to process standard text blocks ('style': 'normal' or default '_type': 'block')
# This skips images, lists, and other complex blocks for simple text extraction
if block.get('_type') == 'block' and block.get('style') == 'normal':

# A block contains an array of 'children' (span, strong, italic, etc.)
text_parts = []
for child in block.get('children', []):
# We extract the 'text' value from each child span
if child.get('_type') == 'span' and 'text' in child:
text_parts.append(child['text'])

# Join the spans within the block
if text_parts:
concatenated_body += "".join(text_parts) + " "

final_body = concatenated_body.strip()

# 4. Create the new structured item
processed_item = {
"id": doc_id,
"title": title,
"slug": slug,
"image_url": image_url,
"concatenated_body": final_body, # The fully stitched text body
}
output.append(processed_item)

# 5. # 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: ""
}
]
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”

  • 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, Sanity, Contentful or Contentstack provide:

  • Zapier doesn’t provide an “Entry Published” and “Updated Entry” trigger that you could use in the same way we are using the “Shopify: Blog Post created” trigger in this tutorial.

    1. Sanity provides webhooks that you can use in Zapier as triggers to keep your content in Nosto up to date.

    2. Just copy the Shopify workflow from the linked tutorial, 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.

Did this answer your question?