# Automate anything with Functions https://www.sanity.io/learn/course/day-one-with-sanity-studio/create-a-function.md The content lifecycle goes beyond authoring in Sanity Studio or rendering on a web page. Automate everything that happens next with Sanity 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. ## Functions vs Document Actions Sanity Studio contains the concept of "Document Actions," where you can configure alternative logic or buttons in the bottom right of the document viewer. To overwrite or augment the built-in Publish, Delete, Discard and other buttons. Functions are triggered by mutations in the Dataset, therefore they will always run whenever a document is changed from any source. Document Actions in the Studio require an author to press a button in order to run logic. Also consider that Document Actions run client-side where it is more difficult to perform long-running processes. And their logic must be bundled in the Studio code which makes secure logic that requires secrets more complicated. 1. [Document actions](https://www.sanity.io/learn/studio/document-actions) can still be useful, you may read more about them in the documentation. ## Introducing Blueprints 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. 1. **Run** the following command to create a new Blueprint file ```sh # in apps/studio pnpm dlx sanity@latest blueprints init --type ts ``` 1. **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`. 1. **Run** the following command to install required packages ```sh # in apps/studio pnpm add @sanity/client @sanity/blueprints ``` 1. Read more about configuration options for [Functions](https://www.sanity.io/learn/compute-and-ai/functions-introduction) in the documentation ## Create a function Before updating the blueprint let's use the Sanity CLI to scaffold a new function. 1. **Run** the following command to create a new Function ```sh # 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: ```text 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 ``` ### Update your blueprint 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`. 1. **Update** your `sanity.blueprint.ts` to include the function you just created, with a filter ```tsx:apps/studio/sanity.blueprint.ts import {defineBlueprint, defineDocumentFunction} from '@sanity/blueprints' export default defineBlueprint({ resources: [ defineDocumentFunction({ name: 'first-published', event: { includeAllVersions: true, on: ['create', 'update'], filter: '_type == "event" && !defined(firstPublished)', }, }), ], }) ``` 1. 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! 1. See [Functions quick start](https://www.sanity.io/learn/compute-and-ai/function-quickstart) for details on how to `destroy` functions ## Test a function If you open the function that was scaffolded for you, you'll see all it does is log the current time to the console. ```typescript:apps/studio/functions/first-published/index.ts 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}`) }) ``` 1. **Run** the following in your terminal to see the event handler ```sh # in apps/studio pnpm dlx sanity@latest functions test first-published ``` Unsurprisingly, you should see a response something like this below: ```sh 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. ### Configuring Sanity Client 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. 1. **Update** the function to set a value on the document which called the function ```typescript:apps/studio/functions/first-published/index.ts 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) } }) ``` 1. 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. ### Test with data 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. 1. 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](https://www.sanity.io/learn/course/day-one-with-sanity-studio/getting-started) the command below should work. Otherwise update the document ID to one in your dataset. 1. **Run** the following command to download a document to a local JSON file ```sh # in apps/studio pnpm dlx sanity@latest documents get AUoLUkEDo6CVeRx5svBjGN > sample-document.json ``` 1. **Run** the following to test your function using the sample document ```sh # in apps/studio pnpm dlx sanity@latest functions test first-published --file sample-document.json --with-user-token ``` 1. 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. ```sh Logs: Dry run: firstPublished set on AUoLUkEDo6CVeRx5svBjGN ``` ## Deploy a function Now you've configured your function and tested it works, it's time to go live. Functions are deployed along with your blueprint. 1. **Run** the following in your console to deploy your blueprint and its function ```sh # in apps/studio pnpm dlx sanity@latest blueprints deploy ``` After a few moments you should receive confirmation that the blueprint has deployed. ```sh Deployment completed! ``` ### Inspecting logs 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. 1. **Run** the following in the terminal to view logs for your `first-published` function ```sh # 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. ```sh 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. ![Warning showing missing field in schema](https://cdn.sanity.io/images/3do82whm/next/fb98fed5b3e58495cd18004196e256659c8d7e67-1316x692.png) A good reminder that your Sanity Studio is just a "versioned window" into the Content Lake! ### Update your schema One last chore before you're finished, let's fix this warning by adding this field to `event` type documents. 1. **Update** the `event` document schema to include a `firstPublished` field ```typescript:apps/studio/schemaTypes/eventType.ts export const eventType = defineType({ // ...all other config fields: [ // ...all other fields defineField({ name: 'firstPublished', description: 'Automatically set when first published', type: 'datetime', readOnly: true, }) ], }) ``` 1. **Run** the following from the terminal to redeploy your Sanity Studio ```sh pnpm dlx sanity@latest deploy ``` ## Functions inspiration Sanity Exchange contains many ready-made Functions "Recipes" which you can add to your project. 1. See [Functions Recipes](https://www.sanity.io/schemas) on Sanity Exchange 2. Explore `/examples` in the [Sanity repo on GitHub](https://github.com/sanity-io/sanity/tree/main/examples) 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.