Meet Agent Actions: Sanity's AI APIs, built for structured content. They understand your content model and fit seamlessly into your workflows.
Ken Jones
Community Experience Engineer at Sanity
Published
Ever wished AI could work directly with your structured content instead of operating in isolation? Agent Actions solve this problem by:
As part of Sanity's Content Operating System, Agent Actions run directly in the Content Lake alongside your content, allowing you to trigger automation from Studio, Functions, Apps, your frontend, or anywhere your code lives. Use Agent Actions to adapt content to business needs across applications.
At the time of this writing, Sanity offers three specialized Agent Actions that work with your schema, not against it: Generate, Transform, and Translate.
When you need fresh content created that aligns with your schema, Generate is your go-to action. It creates new structured content based on instructions and context you provide.
const favoritePizza = "Detroit Style"; await client.agent.action.generate({ schemaId: "your-schema-id", targetDocument: { operation: "create", _type: "menuItem" }, instruction: "Write a description for a $pizzaType pizza.", instructionParams: { pizzaType: { type: "constant", value: favoritePizza } } });
With Generate, you can:
When you need to modify an existing document's content, is what you want. It applies changes to documents while respecting the original structure and formatting.
await client.agent.action.transform({ schemaId: "your-schema-id", documentId: "pizza-123", instruction: "Rewrite this menu item to cater toward $audience.", instructionParams: { audience: 'An Italian pizza connoisseur' } });
Transform excels at:
When you need content in multiple languages, makes the process seamless. It's a specialized version of Transform designed with internationalization in mind.
await client.agent.action.translate({ schemaId: "your-schema-id", documentId: "pizza-123", targetDocument: { operation: "create" }, fromLanguage: { id: "en-US", title: "English" }, toLanguage: { id: "it-IT", title: "Italian" }, styleGuide: "Translate with passion like an Italian pizzaiolo.", protectedPhrases: ["Margherita", "Napoletana", "Quattro Formaggi"], });
Translate supports:
Let's walk through setting up and using Agent Actions in your project.
Before we begin, you'll need:
@sanity/client
v7.1.0 or highersanity
CLI v3.88.0 or higherprojectId
and dataset
nameAgent Actions require a schemaId
to understand your content model. First, check if you've already deployed your schema:
sanity schema list
If you don't see your schema (or want to update it), deploy it:
# Deploy just the schema sanity schema deploy # Or deploy your entire studio sanity deploy
Once deployed, run sanity schema list
again and copy the schema ID. It'll look something like _.schemas.default
.
Create a file for your Agent Action code and set up the Sanity client:
import { createClient } from "@sanity/client"; const client = createClient({ projectId: "your-project-id", dataset: "your-dataset", apiVersion: "vX", // At the moment "vX" API version is required for Agent Actions, but this is just temporary until out of beta token: "your-token" });
Now, let's write an instruction for Generate to create a new blog post:
await client.agent.action.generate({ schemaId: "your-schema-id", targetDocument: { operation: "create", _type: "post" }, instruction: "Write a blog post about $pizzaTopic with a catchy title and pizza metaphors.", instructionParams: { pizzaTopic: { type: "constant", value: "The History of Pizza" } } });
This will create a new draft post with AI-generated content based on your instruction. The content will be structured according to your schema, so fields like title
, body
, and others will be populated and formatted appropriately.
Let's see how Transform differs from Generate:
await client.agent.action.transform({ schemaId: "your-schema-id", documentId: "pizza-post-123", instruction: "Replace 'New York style' with 'Detroit style'.", target: [ path: ['title', 'body'] ] });
This instruction will update only the specified fields, keeping everything else exactly as it was.
And here's how you'd use Translate to create an Italian version of a post:
await client.agent.action.translate({ schemaId: "your-schema-id", documentId: "pizza-post-123", targetDocument: { operation: "create" }, fromLanguage: { id: "en-US", title: "English" }, toLanguage: { id: "it-IT", title: "Italian" }, styleGuide: "Translate with passion like an Italian pizzaiolo.", protectedPhrases: ["Margherita", "Napoletana", "Quattro Formaggi"], });
Here's how Agent Actions solve actual content challenges:
Imagine you have a product catalog with hundreds of items but minimal descriptions. You could use Generate to create draft descriptions based on product attributes:
// For each product without a description const products = await client.fetch(`*[_type == "product" && !defined(description)]._id`); for (const productId of products) { await client.agent.action.generate({ schemaId: "your-schema-id", documentId: productId, instruction: ` Write a compelling product description based on $product. Highlight key features and benefits in a persuasive way. `, instructionParams: { product: { type: "document", documentId: productId } }, target: { path: "description" } }); }
When expanding to new markets, you could set up a that automatically creates translated versions when a post is published:
// In a Sanity Function triggered on publish export const handler = async ({ context, event }) => { // Only translate posts if (event.data._type !== 'post') return; // Create translations for multiple languages const languages = [ { id: 'es-ES', title: 'Spanish' }, { id: 'fr-FR', title: 'French' }, { id: 'de-DE', title: 'German' } ]; for (const language of languages) { await client.agent.action.translate({ schemaId: "your-schema-id", documentId: event.data._id, targetDocument: { operation: "create" }, fromLanguage: { id: "en-US", title: "English" }, toLanguage: language, styleGuide: "Preserve tone and technical accuracy." }); } };
Create a button that generates content suggestions right in the Studio:
// GenerateButton.tsx import {Button, Stack, TextArea, Text} from '@sanity/ui' import {useClient, StringInputProps, useFormValue} from 'sanity' export const GenerateButton = (props: StringInputProps) => { const {value = '', elementProps} = props const id = useFormValue(['_id']) as string // Access document values const client = useClient({apiVersion: 'vX'}).withConfig({useCdn: false}) const handleGenerate = async (event: React.MouseEvent<HTMLButtonElement>) => { try { await client.agent.action.generate({ schemaId: '_.schemas.default', documentId: id, instruction: 'Suggest improvements to the "overview" to make it more engaging.', target: { path: 'aiSuggestions', }, conditionalPaths: { defaultReadOnly: false, }, }) } catch { console.log('Error running action') } } return ( <Stack space={3}> <TextArea rows={7} {...elementProps} value={typeof value === 'string' ? value : ''} /> <Button onClick={handleGenerate} text={'Get AI Suggestions'} /> </Stack> ) }
// movie.schema.ts // ... Other fields defineField({ name: 'overview', title: 'Overview', type: 'blockContent', group: 'main', }), defineField({ name: 'aiSuggestions', title: 'AI Suggestions', type: 'text', group: 'main', components: { input: GenerateButton, }, readOnly: () => true, }),
Agent Actions transform (pun intended) the way you think about automating your content creation workflows. No more hacking around AI outputs and wrestling with content that doesn't mesh with your content's schema.
Key advantages:
Whether you're creating fresh content, standardizing existing assets, or expanding to new languages, Agent Actions help you put content at the core of your business.
Ready to start? Check out the complete documentation.
What will you build with Agent Actions? We'd love to see your implementations. Join us on Discord to discuss your projects, ask questions, or share tips.
const favoritePizza = "Detroit Style";
await client.agent.action.generate({
schemaId: "your-schema-id",
targetDocument: { operation: "create", _type: "menuItem" },
instruction: "Write a description for a $pizzaType pizza.",
instructionParams: {
pizzaType: {
type: "constant",
value: favoritePizza
}
}
});
await client.agent.action.transform({
schemaId: "your-schema-id",
documentId: "pizza-123",
instruction: "Rewrite this menu item to cater toward $audience.",
instructionParams: {
audience: 'An Italian pizza connoisseur'
}
});
await client.agent.action.translate({
schemaId: "your-schema-id",
documentId: "pizza-123",
targetDocument: { operation: "create" },
fromLanguage: { id: "en-US", title: "English" },
toLanguage: { id: "it-IT", title: "Italian" },
styleGuide: "Translate with passion like an Italian pizzaiolo.",
protectedPhrases: ["Margherita", "Napoletana", "Quattro Formaggi"],
});
sanity schema list
# Deploy just the schema
sanity schema deploy
# Or deploy your entire studio
sanity deploy
import { createClient } from "@sanity/client";
const client = createClient({
projectId: "your-project-id",
dataset: "your-dataset",
apiVersion: "vX", // At the moment "vX" API version is required for Agent Actions, but this is just temporary until out of beta
token: "your-token"
});
await client.agent.action.generate({
schemaId: "your-schema-id",
targetDocument: {
operation: "create",
_type: "post"
},
instruction: "Write a blog post about $pizzaTopic with a catchy title and pizza metaphors.",
instructionParams: {
pizzaTopic: {
type: "constant",
value: "The History of Pizza"
}
}
});
await client.agent.action.transform({
schemaId: "your-schema-id",
documentId: "pizza-post-123",
instruction: "Replace 'New York style' with 'Detroit style'.",
target: [
path: ['title', 'body']
]
});
await client.agent.action.translate({
schemaId: "your-schema-id",
documentId: "pizza-post-123",
targetDocument: { operation: "create" },
fromLanguage: { id: "en-US", title: "English" },
toLanguage: { id: "it-IT", title: "Italian" },
styleGuide: "Translate with passion like an Italian pizzaiolo.",
protectedPhrases: ["Margherita", "Napoletana", "Quattro Formaggi"],
});
// For each product without a description
const products = await client.fetch(`*[_type == "product" && !defined(description)]._id`);
for (const productId of products) {
await client.agent.action.generate({
schemaId: "your-schema-id",
documentId: productId,
instruction: `
Write a compelling product description based on $product.
Highlight key features and benefits in a persuasive way.
`,
instructionParams: {
product: {
type: "document",
documentId: productId
}
},
target: {
path: "description"
}
});
}
// In a Sanity Function triggered on publish
export const handler = async ({ context, event }) => {
// Only translate posts
if (event.data._type !== 'post') return;
// Create translations for multiple languages
const languages = [
{ id: 'es-ES', title: 'Spanish' },
{ id: 'fr-FR', title: 'French' },
{ id: 'de-DE', title: 'German' }
];
for (const language of languages) {
await client.agent.action.translate({
schemaId: "your-schema-id",
documentId: event.data._id,
targetDocument: { operation: "create" },
fromLanguage: { id: "en-US", title: "English" },
toLanguage: language,
styleGuide: "Preserve tone and technical accuracy."
});
}
};
// GenerateButton.tsx
import {Button, Stack, TextArea, Text} from '@sanity/ui'
import {useClient, StringInputProps, useFormValue} from 'sanity'
export const GenerateButton = (props: StringInputProps) => {
const {value = '', elementProps} = props
const id = useFormValue(['_id']) as string // Access document values
const client = useClient({apiVersion: 'vX'}).withConfig({useCdn: false})
const handleGenerate = async (event: React.MouseEvent<HTMLButtonElement>) => {
try {
await client.agent.action.generate({
schemaId: '_.schemas.default',
documentId: id,
instruction: 'Suggest improvements to the "overview" to make it more engaging.',
target: {
path: 'aiSuggestions',
},
conditionalPaths: {
defaultReadOnly: false,
},
})
} catch {
console.log('Error running action')
}
}
return (
<Stack space={3}>
<TextArea rows={7} {...elementProps} value={typeof value === 'string' ? value : ''} />
<Button onClick={handleGenerate} text={'Get AI Suggestions'} />
</Stack>
)
}
// movie.schema.ts
// ... Other fields
defineField({
name: 'overview',
title: 'Overview',
type: 'blockContent',
group: 'main',
}),
defineField({
name: 'aiSuggestions',
title: 'AI Suggestions',
type: 'text',
group: 'main',
components: {
input: GenerateButton,
},
readOnly: () => true,
}),