Automate anything with Functions

There are many reasons that you may want some other action to take place after an author presses publish. For example, update a search index or invalidate a website cache.
Sanity has long had webhooks that have allowed you to automate a request to a serverless function to create "if this then that" functionality.
With the launch of Sanity Functions, we now provide our own compute layer, so you can define how your function works in code along with the rest of your Sanity project.
In this lesson you'll create a simple function to write the date and time a document was first published to a field on the document.
Configuring and deploying Sanity Functions requires the creation of a Sanity Blueprint. A new configuration file which will eventually become the central source of truth for all your content operations.
For now it has the sole task of configuring Functions.
# in apps/studiopnpm dlx sanity@latest blueprints init --type ts
You should now have a sanity.blueprint.ts
file at the root of your Sanity Studio. An example function is commented out.
The previous command also added a new dependency @sanity/blueprints
which needs to be installed. The function you'll write also requires @sanity/client
.
# in apps/studiopnpm install @sanity/client @sanity/blueprints
Before updating the blueprint let's use the Sanity CLI to scaffold a new function.
# in apps/studiopnpm dlx sanity@latest blueprints add function --name first-published --installer pnpm --fn-type document-publish
Double-check you have this folder and file structure for your project:
day-one/└── apps/ ├── studio/ -> Sanity Studio │ ├── sanity.blueprint.ts -> Sanity Blueprint │ └── functions/ │ └── first-published/ -> Sanity Function │ └── index.ts ├── tickets/ -> Sanity App SDK app └── web/ -> Next.js app
After the install completes you'll see instructions in the terminal to update your Blueprint file to include this function.
Without any further configuration, this function would be executed on every publish event for every document in every dataset—not ideal.
Let's add the function to the blueprint with an additional GROQ filter
to limit executions to only event
type documents which do not yet have a field named firstPublished
.
sanity.blueprint.ts
to include the function you just created, with a filterimport {defineBlueprint, defineDocumentFunction} from '@sanity/blueprints'
export default defineBlueprint({ resources: [ defineDocumentFunction({ name: 'first-published', event: { on: ['publish'], filter: '_type == "event" && !defined(firstPublished)', }, }), ],})
destroy
functionsIf you open the function that was scaffolded for you, you'll see all it does is log the current time to the console.
import { documentEventHandler } from '@sanity/functions'
export const handler = documentEventHandler(async ({ context, event }) => { const time = new Date().toLocaleTimeString() console.log(`👋 Your Sanity Function was called at ${time}`)})
# in apps/studiopnpm dlx sanity@latest functions test first-published
Unsurprisingly, you should see a response something like this below:
Logs:👋 Your Sanity Function was called at 1:49:48 PM
Excellent! If deployed, this function would write this log every time an event
type document without a firstPublished
field is published.
Not very useful. Let's make the function useful.
The context
parameter in the event handler contains details on a project ID and dataset, and in production a token with permissions to write to documents.
The event
parameter contains details about the document being published.
Combined you can use these details to create a Sanity Client instance and use it to update the document.
import {createClient} from '@sanity/client'import {documentEventHandler} from '@sanity/functions'
export const handler = documentEventHandler(async ({context, event}) => { try { await createClient({ ...context.clientOptions, useCdn: false, apiVersion: '2025-05-08', }) .patch(event.data._id) .setIfMissing({ firstPublished: new Date().toISOString(), }) .commit({dryRun: context.local}) console.log(context.local ? 'Dry run:' : 'Updated:', `firstPublished set on ${event.data._id}`) } catch (error) { console.error(error) }})
.commit()
method with the dryRun
flag, so the mutation is only attempted locally, but will execute when deployed.Your Sanity Function is invoked when a document is published and will receive that document as a parameter—data
. This will be automatic in production, but needs to be manual in development.
projection
to your function configuration to limit or modify the data passed from a document to the function.Fortunately you can feed a JSON file to a function locally. Even better, Sanity CLI makes it simple to download an existing document from your dataset.
If you used the seed data in Local development the command below should work. Otherwise update the document ID to one in your dataset.
# in apps/studiopnpm dlx sanity@latest documents get AUoLUkEDo6CVeRx5svBjGN > sample-document.json
# in apps/studiopnpm dlx sanity@latest functions test first-published --file sample-document.json --with-user-token
--with-user-token
argument is required to pass a token
to the Sanity Client config to perform the mutationYou should receive confirmation that the document would have been modified, but only a "dry run" was performed.
Logs:Dry run: firstPublished set on AUoLUkEDo6CVeRx5svBjGN
Now you've configured your function and tested it works, it's time to go live. Functions are deployed along with your blueprint.
# in apps/studiopnpm dlx sanity@latest blueprints deploy
After a few moments you should receive confirmation that the blueprint has deployed.
Deployment completed!
Open any event
document in your Sanity Studio (whether in local development or the deployed Studio), make a small change and click publish.
You can now access the same logs you saw in local development in production.
first-published
function# in apps/studiopnpm dlx sanity@latest functions logs first-published
You should see something like this in your terminal, confirming the field was set.
7/2/2025 10:25:07 AM INFO Updated: firstPublished set on AUoLUkEDo6CVeRx5svBdUP
If you scroll to the bottom of that document in Sanity Studio you should also see this warning showing there's a value written to the document in Content Lake that is not yet present in your Sanity Studio schema.
A good reminder that your Sanity Studio is just a "versioned window" into the Content Lake!
One last chore before you're finished, let's fix this warning by adding this field to event
type documents.
event
document schema to include a firstPublished
fieldexport const eventType = defineType({ // ...all other config fields: [ // ...all other fields defineField({ name: 'firstPublished', description: 'Automatically set when first published', type: 'datetime', readOnly: true, }) ],})
pnpm dlx sanity@latest deploy
Sanity Exchange contains many ready-made Functions "Recipes" which you can add to your project.
/examples
in the Sanity repo on GitHubProgrammatic mutations are cool, but with access to built-in AI tooling we can perform truly dynamic actions. Let's look at AI Agent Actions next.