TracksMastering Content OperationsCoursesDay One Content OperationsAutomate anything with Functions
Day One Content Operations
Markdown Version

Automate anything with Functions

Log in to watch a video walkthrough of this lesson
Log in
Video thumbnail
The content lifecycle goes beyond authoring in Sanity Studio or rendering on a web page. Automate everything that happens next with Sanity Functions.
Log in to mark your progress for each Lesson and Task

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.

Run the following command to create a new Blueprint file
# in apps/studio
pnpm dlx sanity@latest blueprints init --type ts
Where in your codebase you place Functions is configurable! In this course you'll put them inside the Sanity Studio folder because it plays nicely with default settings.

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.

Run the following command to install required packages
# in apps/studio
pnpm install @sanity/client @sanity/blueprints
Read more about configuration options for Functions in the documentation

Before updating the blueprint let's use the Sanity CLI to scaffold a new function.

Run the following command to create a new Function
# in apps/studio
pnpm 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.

Update your sanity.blueprint.ts to include the function you just created, with a filter
import {defineBlueprint, defineDocumentFunction} from '@sanity/blueprints'
export default defineBlueprint({
resources: [
defineDocumentFunction({
name: 'first-published',
event: {
on: ['publish'],
filter: '_type == "event" && !defined(firstPublished)',
},
}),
],
})
If your function writes changes to a document it is extremely important to configure your filter correctly. You don't want to deploy recursive functions!
See Functions quick start for details on how to destroy functions

If 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}`)
})
Run the following in your terminal to see the event handler
# in apps/studio
pnpm 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.

Update the function to set a value on the document which called the function
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)
}
})
This function can write to Content Lake, even if tested with a local document, because we're going to use a real document ID. For this reason we're protecting the .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.

Add a 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.

Run the following command to download a document to a local JSON file
# in apps/studio
pnpm dlx sanity@latest documents get AUoLUkEDo6CVeRx5svBjGN > sample-document.json
Run the following to test your function using the sample document
# in apps/studio
pnpm dlx sanity@latest functions test first-published --file sample-document.json --with-user-token
The --with-user-token argument is required to pass a token to the Sanity Client config to perform the mutation

You 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.

Run the following in your console to deploy your blueprint and its function
# in apps/studio
pnpm 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.

Run the following in the terminal to view logs for your first-published function
# in apps/studio
pnpm 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.

Update the event document schema to include a firstPublished field
export const eventType = defineType({
// ...all other config
fields: [
// ...all other fields
defineField({
name: 'firstPublished',
description: 'Automatically set when first published',
type: 'datetime',
readOnly: true,
})
],
})
Run the following from the terminal to redeploy your Sanity Studio
pnpm dlx sanity@latest deploy

Sanity Exchange contains many ready-made Functions "Recipes" which you can add to your project.

See Functions Recipes on Sanity Exchange
Explore /examples in the Sanity repo on GitHub

Programmatic 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.

You have 12 uncompleted tasks in this lesson
0 of 12