Sanity logosanity.ioAll Systems Operational© Sanity 2026
Change Site Theme
Sanity logo

Documentation

    • Overview
    • Platform introduction
    • Next.js quickstart
    • Nuxt.js quickstart
    • Astro quickstart
    • React Router quickstart
    • Studio quickstart
    • Build with AI
    • Content Lake
    • Functions
    • APIs and SDKs
    • Visual Editing
    • Blueprints
    • Platform management
    • Dashboard
    • Studio
    • Canvas
    • Media Library
    • App SDK
    • Content Agent
    • HTTP API
    • CLI
    • Libraries
    • Specifications
    • Changelog
    • User guides
    • Developer guides
    • Courses and certifications
    • Join the community
    • Templates
Functions
Overview

  • Introduction
  • Get started

    Create a Document Function
    Create a Media Library Asset Function
    Official recipes

  • Concepts

    Manage dependencies
    Local testing

  • Guides

    Configure @sanity/client
    Add environment variables
    Common patterns

  • Reference

    Handler reference
    CLI reference

On this page

Previous

Add environment variables

Next

Handler reference

Was this page helpful?

On this page

  • Explore the exchange
  • Ping an endpoint on publish
  • Automatically translate documents
  • Set an undefined value with setIfMissing
  • Scope Functions to a specific dataset
  • Use Functions with Media Library assets
  • Enable recursion control in unofficial clients
FunctionsLast updated January 29, 2026

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.

Experimental feature

This article describes an experimental Sanity feature. The APIs described are subject to change and the documentation may not be completely accurate.

Prerequisites:

  • Complete the Functions quick start, or be comfortable writing and deploying a Sanity Function.
  • sanity CLI v4.12.0 or higher is recommended to interact with Blueprints and Functions. You can always run the latest CLI commands with npx sanity@latest.

The examples below 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 the a curated list in the exchange.

Explore more functions

Auto-tag blog posts

AI-powered automatic tagging for Sanity blog posts that analyzes content to generate 3 relevant tags, maintaining consistency by reusing existing tags from your content library.

Algolia Sync

Automatically update your Algolia index

Post to Bluesky

Notify your audience when you publish a new document.

Explore more recipes

See more official and community functions on 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 {
    await fetch(URL)
  } catch (error) {
    console.error(error)
  }
})
export async function handler({context, event}) {
  const URL = process.env.DEPLOY_HOOK_URL
  if (!URL) {
    throw new Error("DEPLOY_HOOK_URL is not set")
  }
  try {
    await fetch(URL)
  } 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:

  • Vercel: Create and trigger deploy hooks
  • Azure: purge content
  • Cloudflare: purge cache

Automatically translate documents

You can combine Agent Actions Translate with Functions to translate documents automatically.

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

The GROQ filter in this example is important. It makes sure that the function only runs when the language is set to English. When we generate a new translation in the next code block, Translate sets that field to Greek. This stops the new document from triggering the same function and creating a recursive loop.

You could also create draft or version documents to prevent the "on publish" function from triggering.

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

  • Import and configure the @sanity/client.
  • Capture the document data from the event.
  • Construct a translate request.
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)
  }
})
import { createClient } from '@sanity/client'

export async function handler({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, it will create a Greek version. Learn more about Agent Actions here.

Set an undefined value with setIfMissing

You may have values in documents that are sometimes set by people, but otherwise could be derived programatically. This example uses GROQ's !defined function and a setIfMissing patch to add a 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.

"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 to 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: "2025-02-19"
  })
  
  try {
    await client.patch(data._id, {
      setIfMissing: {
        firstPublished: new Date().toISOString()
      }
    })
  } catch (error) {
    console.error(error)
  }
})
import { createClient } from '@sanity/client'

export async function handler({context, event}) {
  const { data } = event
  const client = createClient({
    ...context.clientOptions,
    apiVersion: "2025-02-19"
  })
  
  try {
    await client.patch(data._id, {
      setIfMissing: {
        firstPublished: new Date().toISOString()
      }
    })
  } 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.

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

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.
  • Update references when assets are deleted.

If you're using the TS/JS configuration format, you'll need to update @sanity/blueprints to v0.4.0 or later to access defineMediaLibaryAssetFunction.

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.

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: 'mlFqEeKZYecz',
        }
      },
    })
  ],
})

If you need to query or mutate ML 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.

  • Article
  • Changelog
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 {
    await fetch(URL)
  } catch (error) {
    console.error(error)
  }
})
export async function handler({context, event}) {
  const URL = process.env.DEPLOY_HOOK_URL
  if (!URL) {
    throw new Error("DEPLOY_HOOK_URL is not set")
  }
  try {
    await fetch(URL)
  } catch (error) {
    console.error(error)
  }
}
import {defineBlueprint, defineDocumentFunction} from '@sanity/blueprints'

export default defineBlueprint({
  resources: [
    defineDocumentFunction({
      name: "translate",
      event: {
        on: ["publish"],
        filter: "_type == 'post' && language == 'en-US'",
        projection: "{_id}"
      }
    }),
  ],
})
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)
  }
})
import { createClient } from '@sanity/client'

export async function handler({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)
  }
}
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: "2025-02-19"
  })
  
  try {
    await client.patch(data._id, {
      setIfMissing: {
        firstPublished: new Date().toISOString()
      }
    })
  } catch (error) {
    console.error(error)
  }
})
import { createClient } from '@sanity/client'

export async function handler({context, event}) {
  const { data } = event
  const client = createClient({
    ...context.clientOptions,
    apiVersion: "2025-02-19"
  })
  
  try {
    await client.patch(data._id, {
      setIfMissing: {
        firstPublished: new Date().toISOString()
      }
    })
  } catch (error) {
    console.error(error)
  }
}
import {defineBlueprint, defineDocumentFunction} from '@sanity/blueprints'
export default defineBlueprint({
  resources: [
    defineDocumentFunction({
      name: "log-event",
      event: {
        on: ["update"],
        filter: "_type == 'post'",
        resource: {
          type: 'dataset',
          id: 'myProjectId.production'
        },
      },
    })
  ]
})
import {defineBlueprint, defineDocumentFunction} from '@sanity/blueprints'
export default defineBlueprint({
  resources: [
    defineDocumentFunction({
      name: "log-event",
      event: {
        on: ["update"],
        filter: "_type == 'post' && sanity::dataset() == 'production'",
      },
    })
  ]
})
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: 'mlFqEeKZYecz',
        }
      },
    })
  ],
})