Sanity Agent Actions are the API-side counterpart to the editor-facing AI Assist feature. Where AI Assist lives inside Sanity Studio and surfaces to editors as inline buttons, Agent Actions let you call AI workflows programmatically — from a Next.js route handler, a server action, a cron job, or any process that can reach the Sanity API. That distinction matters a lot when you need automation that runs without a human clicking anything.
I have been using Agent Actions since they moved out of early access in early 2026. This post covers what they actually do, when to reach for them instead of AI Assist, and a concrete example wired into a Next.js App Router server action.
What Sanity Agent Actions are (and are not)
Agent Actions are a Sanity platform feature that exposes AI-driven document operations through the Sanity client or HTTP API. You describe an instruction — "translate this document's body to French", "generate an SEO meta description from this article", "extract product specs from this raw text field" — and the platform applies it to one or more documents, writing the result back into your dataset as a draft or published document depending on how you configure the call.
They are distinct from AI Assist in two ways:
- Trigger point. AI Assist is triggered by an editor inside Studio. Agent Actions are triggered by code you control — a server action, webhook handler, scheduled job, anything.
- Scope. Agent Actions can operate across multiple documents in a single invocation. You can feed them context beyond the document being mutated (reference material, a system prompt, external data), which is what makes them useful for batch enrichment and structured generation rather than just per-field suggestions.
The official capability surface is documented at sanity.io/docs/agent-actions. Before wiring up any method signature, check there — the API was still receiving updates as of mid-2026.
When to use Agent Actions vs AI Assist
Use AI Assist when the trigger is an editor making a deliberate choice. A writer wants to improve a paragraph, generate alt text for an uploaded image, or get a first-draft introduction. That is a human-in-the-loop workflow and AI Assist is the right tool.
Use Agent Actions when:
- You want automation that fires without editor intervention (on publish, on a schedule, via webhook).
- You need to process a batch of documents — re-translate every article after a brand voice update, backfill a new
summaryfield across 400 existing posts. - The trigger comes from outside Studio — a form submission, a product import, a CMS-to-CMS migration.
- You want to attach extra context to the AI call that is not in the document itself — a system prompt stored in your codebase, data fetched from an external API, content from several related documents.
In short: if an editor would have to click the same thing repeatedly, that is a candidate for Agent Actions.
A realistic example: auto-enriching new articles on publish
The scenario: every time an editor publishes a new article document, I want to automatically generate a metaDescription and a tldr field using the document's title and body, without the editor having to do anything.
The wiring: Sanity webhook → Next.js route handler → Agent Actions call.
First, the route handler that receives the webhook.
// app/api/sanity/on-publish/route.ts
import { type NextRequest, NextResponse } from 'next/server'
import { createClient } from '@sanity/client'
import { isValidSignature, SIGNATURE_HEADER_NAME } from '@sanity/webhook'
const client = createClient({
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!,
dataset: process.env.NEXT_PUBLIC_SANITY_DATASET!,
apiVersion: '2026-06-01',
token: process.env.SANITY_API_WRITE_TOKEN,
useCdn: false,
})
const WEBHOOK_SECRET = process.env.SANITY_WEBHOOK_SECRET!
export async function POST(req: NextRequest) {
const rawBody = await req.text()
const signature = req.headers.get(SIGNATURE_HEADER_NAME) ?? ''
const valid = await isValidSignature(rawBody, signature, WEBHOOK_SECRET)
if (!valid) return NextResponse.json({ error: 'Invalid signature' }, { status: 401 })
const body = JSON.parse(rawBody) as { _id: string; _type: string }
if (body._type !== 'article') return NextResponse.json({ ok: true })
// Fire-and-forget enrichment — respond fast, process async
enrichArticle(body._id).catch(console.error)
return NextResponse.json({ ok: true })
}
async function enrichArticle(documentId: string) {
// Agent Actions are accessed via the client's `agent` namespace.
// Consult https://www.sanity.io/docs/agent-actions for the current method
// signatures — the API surface was still evolving as of mid-2026.
await (client as any).agent.action.generate({
schemaId: 'sanity.workspace.schema',
documentId,
// target specifies which fields to populate
target: [
{ path: 'metaDescription' },
{ path: 'tldr' },
],
// instruction is your system-level prompt context
instruction: `
You are an SEO and editorial assistant.
Write a concise metaDescription (120-155 chars) and a one-sentence tldr
for the article. Use the title and body fields as your source.
Do not invent facts not present in the body.
`,
})
}
A few things to note here. I cast client to any for the agent namespace because @sanity/client typings for Agent Actions were not fully shipped in the stable TS definitions at the time of writing — check the current package version before copying this. The schemaId value 'sanity.workspace.schema' is what the platform uses to understand your field types; the exact string is in the Agent Actions docs. The call writes results back as a draft on the document, so the editor still sees and approves the generated content before it is live.
Adding extra context beyond the document
One capability that makes Agent Actions more powerful than AI Assist for automation is the ability to inject context. Suppose you have a brand voice guide stored as a plain text file in your repo, or you want to include content from a related author reference.
// enrichArticle with injected brand context
import { readFileSync } from 'fs'
import path from 'path'
async function enrichArticleWithContext(documentId: string) {
const brandVoice = readFileSync(
path.join(process.cwd(), 'content/brand-voice.md'),
'utf-8'
)
await (client as any).agent.action.generate({
schemaId: 'sanity.workspace.schema',
documentId,
target: [{ path: 'metaDescription' }, { path: 'tldr' }],
instruction: `
Brand voice guidelines:\n${brandVoice}\n\n
Write the metaDescription and tldr in this brand voice.
120-155 chars for metaDescription. One sentence for tldr.
`,
})
}
This is where Agent Actions pull ahead of anything you can do inside Studio today. Feeding a brand guide, a competitor analysis, related document content, or external API data as part of the instruction context is a pattern that unlocks genuinely useful automation rather than just field-level autocomplete.
What to watch out for
Rate limits. If you trigger enrichment on batch publishes (a content import of 200 documents), you will hit the Sanity API rate limits quickly. Queue the work — use a simple array with a delay between calls, or push document IDs onto a queue (Upstash QStash works well here) and process them one at a time.
Prompt drift. The instruction string in your code is effectively a dependency. Version-control it, treat changes with the same care as schema migrations, and consider externalising long prompts to a file rather than an inline template literal.
Always draft, never auto-publish. Agent Actions write to drafts by default. Keep it that way. Automated content going directly to published without editor review is a risk not worth taking for most sites.
Type safety. Until @sanity/client exports stable types for the agent namespace, keep your Agent Actions calls isolated in a single module so the any cast is contained and easy to replace when types ship.
Agent Actions are a genuinely useful addition to the Sanity platform for teams that need repeatable AI automation outside the Studio UI. The webhook-to-route-handler pattern above is the simplest reliable wiring for Next.js projects — extend it with queueing once your publish volume grows beyond a handful of documents at a time.
Top comments (0)