Functions

Functions cheat sheet

Common patterns and techniques for creating Functions.

Functions create the ability for countless content-driven opportunities. This guide collects common patterns and approaches to working with Functions.

Prerequisites:

  • Complete the Functions quick start, or be comfortable writing and deploying a Sanity Function.
  • The latest version of the sanity CLI (sanity@latest) is recommended to interact with Blueprints and Functions as shown in this guide. You can always run the latest CLI commands with npx sanity@latest.

The examples on this page assume you've created a new function and configured it to trigger based on your own schema requirements.

Explore the Exchange

Looking for more ideas and ready-made functions? Check out a curated list in the Exchange.

Ping an endpoint on publish

A common approach to invalidating CDNs and triggering new builds is to ping, or make a GET request, to an endpoint. Some require you to provide specifics, such as the endpoint or slug for targeted refreshes. Others only require a single URL.

Create a function and configure it to trigger when your target document publishes. For the example, make sure to define an environment variable named DEPLOY_HOOK_URL.

import { documentEventHandler } from '@sanity/functions'

export const handler = documentEventHandler(async ({ context, event }) => {
  const URL = process.env.DEPLOY_HOOK_URL
  if (!URL) {
    throw new Error("DEPLOY_HOOK_URL is not set")
  }
  try {
    const res = await fetch(URL)
    if (!res.ok) console.error(res.status)
  } catch (error) {
    console.error(error)
  }
})

To find the deploy or trigger URL for your provider, check their documentation. We've included a few common links below:

Automatically translate documents

You can combine Agent Actions Translate with Functions to translate documents automatically. This example requires @sanity/client v7.1.0 or later configured with an apiVersion of vX, and a deployed schema to supply the schemaId.

We recommend completing the quick start if you haven't used Translate before.

First, create a function and configure it to only trigger when a document's language is in your "from" language. Here's an example of the function resource in sanity.blueprint.ts.

import {defineBlueprint, defineDocumentFunction} from '@sanity/blueprints'

export default defineBlueprint({
  resources: [
    defineDocumentFunction({
      name: "translate",
      event: {
        on: ["publish"],
        filter: "_type == 'post' && language == 'en-US'",
        projection: "{_id}"
      }
    }),
  ],
})

Use caution when creating documents

For this approach, we have documents with a language set. We only want the English language files.

import { documentEventHandler } from '@sanity/functions'
import { createClient } from '@sanity/client'

export const handler = documentEventHandler(async ({ context, event }) => {
  const { data } = event
  const client = createClient({
    ...context.clientOptions,
    apiVersion: 'vX',
  })
  const targetLanguage = {
    id: "el-GR",
    title: "Greek"
  }
  // Create a consistent ID based on the source and target language.
  // This allows the function to override the document in the future
  const targetId = `${data._id}-${targetLanguage.id}`

  try {

    await client.agent.action.translate({
      // Replace with your schema ID
      schemaId: "your-schema-id",
      
      // Tell the client to run the action asynchronously.
      // We don't need to wait for it to complete.
      async: true,
      
      // Tell the client the ID of the document to use as the source.
      documentId: data._id,

      // Set the language field to the target language.
      languageFieldPath: "language",
      
      // Set the operation mode
      // createOrReplace will override the ID in future invocations.
      targetDocument: { 
        operation: "createOrReplace",
        _id: targetId
      },
      
      // Set the 'from' and 'to' language
      fromLanguage: {id: "en-US", title: "English"},
      toLanguage: {id: targetLanguage.id, title: targetLanguage.title},
    });
  } catch (error) {
    console.error(error)
  }
})

Now, when you publish an English-language document, the function creates a Greek translation. Learn more about Agent Actions.

Set an undefined value with setIfMissing

You may have values in documents that are sometimes set by people, but otherwise could be derived programmatically. This example uses GROQ's defined() function (negated with !) and a setIfMissing patch to add the current date and time as the published date to a document, but only when it hasn't been set.

For this example, you'll need to:

  • Set up a new function or edit an existing one.
  • Import and configure the @sanity/client if you haven't already.

First, modify the following filter and add it to your function's event in the sanity.blueprint.ts configuration.

import {defineBlueprint, defineDocumentFunction} from '@sanity/blueprints'

export default defineBlueprint({
  resources: [
    defineDocumentFunction({
      name: "first-published",
      event: {
        on: ["publish"],
        filter: "_type == 'post' && !defined(firstPublished)",
      },
    }),
  ],
})

Adjust the _type and firstPublished values to match properties from your schema. !defined checks that the property is not set, which prevents the function from running if the document receives future updates.

Next, create a setIfMissing patch to set the same field from the filter. setIfMissing is redundant here, as it should be empty if !defined worked as intended. It's still a useful approach when you only want to update empty fields.

import { documentEventHandler } from '@sanity/functions'
import { createClient } from '@sanity/client'

export const handler = documentEventHandler(async ({ context, event }) => {
  const { data } = event
  const client = createClient({
    ...context.clientOptions,
    apiVersion: "2026-02-27"
  })
  
  try {
    await client.patch(data._id, {
      setIfMissing: {
        firstPublished: new Date().toISOString()
      }
    }).commit()
  } catch (error) {
    console.error(error)
  }
})

Scope Functions to a specific dataset

By default, your functions run against all datasets for the project they've been configured with. You can define a resource in your functions config to cause only changes in a specific dataset to trigger functions. Replace with your project ID.

import {defineBlueprint, defineDocumentFunction} from '@sanity/blueprints'
export default defineBlueprint({
  resources: [
    defineDocumentFunction({
      name: "log-event",
      event: {
        on: ["update"],
        filter: "_type == 'post'",
        resource: {
          type: 'dataset',
          id: '.production'
        },
      },
    })
  ]
})

Alternatively, you can also narrow dataset scope with filters and the sanity::dataset() function.

import {defineBlueprint, defineDocumentFunction} from '@sanity/blueprints'
export default defineBlueprint({
  resources: [
    defineDocumentFunction({
      name: "log-event",
      event: {
        on: ["update"],
        filter: "_type == 'post' && sanity::dataset() == 'production'",
      },
    })
  ]
})

Keep functions from running during an import or sync

A deployed function has no on/off switch. Unlike a webhook, which you can disable in Manage, a function keeps responding to every event that matches its configuration until you change or remove it in your blueprint. Before you import data or sync content into a dataset, use one of these approaches, in order of preference:

  • Scope the function to another dataset: set resource in the function's event configuration to the dataset it should react to, as shown in Scope Functions to a specific dataset. Imports into any other dataset, such as a staging dataset you sync from production, don't trigger it.
  • Remove the function and redeploy: if the function needs to react to the dataset you're importing into, remove it from sanity.blueprint.ts and run sanity blueprints deploy, which removes the deployed function. Add it back and deploy again after the import finishes.
  • Return early from the handler: set an environment variable while the import runs, and have the handler exit before doing any work when the variable is set. This is the quickest approach to turn on and off, but the function still runs for every matching event, so each run counts as an invocation toward your Functions usage.

To use an environment variable as a switch, check it at the start of your handler:

import { documentEventHandler } from '@sanity/functions'

export const handler = documentEventHandler(async ({ context, event }) => {
  // Skip all work while an import or sync is running
  if (process.env.SYNC_IN_PROGRESS === 'true') {
    return
  }

  // Your function logic
})

Set the variable with sanity functions env add before the import, and remove it with sanity functions env remove afterward. Changes take effect on the function's next invocation. Replace FUNCTION_NAME with the name of your function.

npx sanity@latest functions env add FUNCTION_NAME SYNC_IN_PROGRESS true

# Run your import or sync

npx sanity@latest functions env remove FUNCTION_NAME SYNC_IN_PROGRESS

Use Functions with Media Library assets

To configure a function to react to changes in assets in Media Library, use the defineMediaLibraryAssetFunction helper and configure the resource object.

You can try things like:

  • Comparing changes in an asset's aspect data.
  • Kicking off a review flow when new versions are added to an asset.
  • Updating references when assets are deleted.

Asset functions only support the sanity.asset document _type. You can apply additional filters, but it will only run on documents of this type.

For example, this function will run whenever someone deletes an asset that's referenced by another document in your organization. Replace YOUR_MEDIA_LIBRARY_ID with the ID of your Media Library.

import { defineBlueprint, defineMediaLibraryAssetFunction } from '@sanity/blueprints'

export default defineBlueprint({
  resources: [
    defineMediaLibraryAssetFunction({
      name: 'ml-asset',
      event: {
        on: ['delete'],
        filter: "documents::incomingGlobalDocumentReferenceCount() > 0",
        projection: "{_id, versions, title}",
        resource: {
          type: 'media-library',
          id: 'YOUR_MEDIA_LIBRARY_ID',
        }
      },
    })
  ],
})

If you need to query or mutate Media Library documents from your function, make sure to configure the @sanity/client for use with Media Library.

Learn more about working with Media Library documents in the Media Library documentation.

Enable recursion control in unofficial clients

Sanity client (@sanity/client v7.12.0 or later) includes recursion protection by reading and setting a lineage header. If you're mutating documents from a function and not using the client, you can implement this functionality yourself.

  • Read the process.env.X_SANITY_LINEAGE environment variable.
  • Pass the value to the X-Sanity-Lineage header of any requests that mutate a document.

This allows Sanity's infrastructure to limit function invocation chains just as it does for the official client.

Was this page helpful?

Sanity logosanity.ioAll Systems Operational© Sanity 2026
Change site theme