Agent Actions: AI building blocks for structured content (ssr)

Written by Ken Jones

Missing Image!

Why Agent Actions matter

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.

The Agent Actions toolkit

At the time of this writing, Sanity offers three specialized Agent Actions that work with your schema, not against it: Generate, Transform, and Translate.

Generate

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 
    }
  }
});
PortableText [components.type] is missing "protip"

With Generate, you can:

Transform

When you need to modify an existing document's content, Transform 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:

Translate

When you need content in multiple languages, Translate 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:

Getting started with Agent Actions

Let's walk through setting up and using Agent Actions in your project.

Prerequisites

Before we begin, you'll need:

Step 1: Deploy your schema

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

Step 2: Configure the client

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"
});
PortableText [components.type] is missing "protip"

Step 3: Create your first instruction

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.

Step 4: Trying Transform

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.

Step 5: Translating content

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"],
});

Real-world use cases

Here's how Agent Actions solve actual content challenges:

Content creation at scale

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"
    }
  });
}

Automated translations

When expanding to new markets, you could set up a Sanity Function 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."
    });
  }
};

Custom Studio components

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,
}),
Sanity Studio input component preview

Final thoughts

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.

Join the conversation

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.