Implementation Patterns7 min read

How to Use Functions to Automate Content Workflows

The failure mode is familiar: a translation job runs three days late because someone forgot to kick off the script, a moderation queue backs up because the webhook handler silently 500'd overnight, and a product feed goes stale because the…

Published July 21, 2026

The failure mode is familiar: a translation job runs three days late because someone forgot to kick off the script, a moderation queue backs up because the webhook handler silently 500'd overnight, and a product feed goes stale because the cron server that enriched it was quietly decommissioned six months ago. Content operations that depend on humans remembering to run automation, or on brittle external glue code stitched to your CMS, break in exactly the places where they hurt most: at scale, under deadline, and in front of customers.

Sanity treats this differently. As the Content Operating System for the AI era, it runs automation as a first-class part of the content backend rather than something you bolt on afterward. Functions let you execute serverless logic in response to content events, so enrichment, validation, translation, and notification happen inside the same governed system where your content already lives.

This guide is a practical walkthrough of using Functions to automate real editorial workflows: what triggers them, how to keep them observable, how to compose them with the App SDK and Content Releases, and where the boundaries sit between automation you should own and automation you should hand to the platform. The goal is fewer forgotten scripts, fewer silent failures, and content operations that scale output instead of headcount.

Why external glue code is the wrong place for content automation

Most teams start automating content the same way: a webhook fires from the CMS, hits a cloud function somewhere, that function calls back into the content API, and a chain of external services carries the work forward. It works in the demo. It rots in production. The webhook secret rotates and nobody updates the consumer. The external function's runtime hits end of life. The retry logic that looked fine handles a single failure but stampedes the API when a whole batch of documents changes at once. Worst of all, none of it is visible from inside the editorial tool, so when a translation doesn't appear, the editor files a ticket and an engineer spends an afternoon reading logs across three systems.

The deeper problem is architectural. When automation lives outside the content platform, it creates a silo. The rules that govern who can change what, when content is allowed to publish, and how changes are audited all live in one place, and the code that acts on content lives somewhere else entirely. Legacy CMSes stop at publishing and leave everything after it to you; that gap is exactly where the glue code accumulates. This maps to the "Automate everything" pillar: automation should be part of the content backend, subject to the same permissions, the same audit trail, and the same event model as everything else, not a parallel universe of scripts that happen to talk to it over HTTP.

Functions collapse that gap. Instead of shipping content events out to code you separately host, monitor, and secure, you run the logic against document events inside the platform. The trigger, the code, and the content it touches share one system, which means one place to reason about failure and one place to see whether the automation actually ran.

How Functions get triggered by content events

The core model is event-driven. Something happens to a document, a create, an update, a publish, and a Function runs in response. Rather than polling for changes or asking editors to press a button, you declare the conditions under which your logic should fire, and the platform invokes it when those conditions are met. This is the difference between a cron job that checks "did anything change in the last hour?" and an event that says "this specific document just published; act on it now."

Filtering matters as much as the trigger. You rarely want a Function to run on every write to every document. You want it to run when a `product` document publishes without a translated description, or when an `article` moves into a review state, or when an author uploads an asset that needs moderation. Because the platform understands your schema and its query language can express exactly those shapes, you can scope a Function narrowly, which keeps it cheap to run and easy to reason about. A GROQ projection inside the handler then pulls precisely the fields the logic needs, references resolved with `->`, arrays flattened with `[...]`, in a single round trip rather than a chain of REST calls to hydrate related content.

The practical payoff is precision. A well-scoped Function is a small, testable unit: given this document event, produce this side effect. That is far easier to own than a monolithic webhook consumer with a switch statement branching on every content type in your project. Start with one narrow trigger, one clear side effect, and expand from there. The narrower the trigger, the fewer surprises in production.

Automating enrichment and translation without a separate pipeline

Enrichment is the workflow that most obviously belongs inside the content backend. When a document publishes, you often want to derive something from it: a translated field set, an SEO summary, extracted entities, alt text for an uploaded image, or a normalized price pulled from an upstream system. Traditionally each of these is its own external service with its own deployment, its own credentials, and its own way of failing. That sprawl is how a "simple" content model ends up depending on five microservices nobody remembers owning.

With Functions plus the App SDK, the enrichment runs where the content lives and writes back through the same governed interface an editor would use. A translation Function can read the source fields with a GROQ query, call a translation provider, and patch the translated fields onto the document, all as one serverless unit. Because the write goes through the platform's normal APIs, it respects your schema validation and shows up in the document's history like any other change. An editor sees the translated content appear in the Studio, not in some opaque side table.

The governance angle is the part external pipelines usually skip. Automated writes are still writes, and they belong in the same audit trail as human edits. Roles & Permissions constrain what a Function can touch, and Audit logs record that it happened. This is what separates automation you can defend in a compliance review from automation that quietly mutates production content with no paper trail. When the enrichment is part of the Content Operating System rather than a script with an API token, "who changed this, and why" always has an answer.

Keeping automation safe with staged writes and Content Releases

The scariest automation is the kind that writes straight to published content with no chance for a human to look first. A bad enrichment rule, an over-eager find-and-replace, a translation provider returning garbage for one locale, any of these can ripple across thousands of live documents before anyone notices. The blast radius of unsupervised automation is the thing that keeps content leads up at night, and it is a legitimate reason teams under-automate.

The answer is not less automation; it is staged automation. A Function can write into a Content Release, a governed batch of changes, rather than straight to the published dataset. The automated work still happens without a human kicking it off, but it lands in a reviewable, schedulable container that an editor can inspect, approve, or discard as a unit. That preserves the human in the loop exactly where judgment matters, at the boundary between "the machine did work" and "the work is live," without dragging a person into every intermediate step.

This composes naturally with scheduling. A Function can prepare a Release of enriched or translated content and schedule it to go live at a set time, or hold it for review. Combined with Visual Editing and the Presentation Tool, a reviewer can see automated changes in the context of the actual rendered page before approving them, rather than eyeballing raw fields. The pattern to internalize: let Functions do the labor, let Content Releases hold the risk, and let a human own the go-live decision. Automate the toil, govern the outcome.

Making Functions observable so silent failures stop hiding

The single most common reason teams distrust content automation is invisibility. A script runs, or doesn't, on a server nobody looks at, and the first signal of failure is a customer complaint or a stakeholder asking why the German site is three versions behind. When your monitoring lives in a different system from your content, every incident becomes a cross-team archaeology dig. The cost is not just the outage; it is the erosion of trust that pushes teams back toward manual processes they can at least see.

Running automation inside the platform changes where you look. Because Functions execute against document events in the same system that holds the content, the relationship between "this document changed" and "this automation ran" is legible from one place. You are not correlating timestamps across a CMS log, a cloud provider dashboard, and a third-party queue. When a Function fails, you want to know which document triggered it, what the input was, and whether a retry is safe, and you want that without SSH access to anything.

Build observability in from the first Function, not after the first incident. Log the document id and the decision the Function made, make handlers idempotent so a retry cannot double-apply a change, and fail loudly rather than swallowing errors to keep a batch moving. An idempotent, well-logged Function is one you can re-run with confidence; a fire-and-forget one is a future incident. The goal of automating a workflow is to remove human toil, not human visibility. Keep the toil out and the visibility in.

Composing Functions into real editorial workflows

A single Function automates a task. A workflow is several of them, plus human checkpoints, arranged so content moves from draft to live with the machine handling the repetitive parts and people handling the judgment. The mistake is trying to build one giant Function that does everything; the durable pattern is small, single-purpose Functions triggered at the right moments in the content lifecycle, each observable and testable on its own.

Consider a product content pipeline. When a `product` publishes, one Function validates required fields and flags gaps. Another enriches it with a generated summary and writes the result into a Content Release. A third notices missing locale translations and queues them. A reviewer opens the Release, checks the automated work in the Presentation Tool, and approves. Scheduling handles the go-live. Nobody wrote a cron job, nobody manually kicked off translation, and every step left a record. Each piece is replaceable without rewriting the whole chain, which is what keeps the system maintainable as requirements shift.

This is where the shape of the platform pays off. Because the Studio is a React app you actually ship, you can surface a Function's status or a manual re-run control right next to the content it acts on, using custom input components and the App SDK. Because TypeGen turns your schema into TypeScript, the Function handling a document knows the exact shape it is working with at compile time, so a schema change that would break the automation surfaces in your editor, not in production. Legacy CMSes make you work their way; here the automation and the editorial surface bend to the workflow you actually run, so you scale output instead of scaling the number of people babysitting scripts.

Content automation approaches: Sanity Functions vs. external glue code on other platforms

FeatureSanityContentfulStrapiPayload
Serverless automation triggerFunctions run against document events (create, update, publish) inside the platform, scoped with GROQ filters so logic fires only on the documents that matter.Webhooks fire on content events to an external endpoint you host and secure; the execution runtime lives outside the platform.Lifecycle hooks and webhooks run in your self-hosted app process or an external service; you own the hosting and scaling.Hooks (beforeChange, afterChange) run in your Node app; execution and hosting are yours to operate and monitor.
Reading related content in the handlerGROQ projection resolves references with -> and flattens arrays with [...] in one round trip, so the handler pulls exactly the shape it needs.GraphQL or REST from the handler; resolving references often means multiple queries or a crafted GraphQL selection set.REST or GraphQL with populate to hydrate relations; deep relations can require several calls or careful populate config.Local API queries with depth control inside the same app; convenient in-process, but tied to your deployment.
Staged automated writes for reviewFunctions can write into a Content Release, a governed, schedulable batch a human approves or discards as a unit before go-live.Releases group content for scheduled publish; wiring automated writes into a reviewable batch is a manual integration effort.Draft and publish states exist; batching automated writes for unified review is something you assemble yourself.Drafts and versions exist; staging automated changes as one reviewable, schedulable set is left to your application logic.
Governance of automated writesAutomated writes respect Roles & Permissions and land in Audit logs alongside human edits, so who-changed-what always has an answer.API-key writes are governed by token scopes; capturing them in a unified audit view depends on your setup and plan.Permissions apply to API tokens; audit trails for automated writes depend on self-hosted logging you configure.Access control applies to API operations; audit history for automation is whatever your app records and retains.
Observability of automation runsFunction execution ties to the document event in one system, so the link between a change and the automation that ran is legible without cross-system log digs.Webhook delivery logs exist, but the downstream function's success or failure lives in your own external monitoring.You instrument logging in your server or service; correlating a run to a content change is your responsibility.Hook execution logs to your app's logging stack; observability quality depends on the infrastructure you run it on.
Type safety against the content modelTypeGen turns the schema into TypeScript, so a Function knows the document shape at compile time and schema drift surfaces before production.TypeScript types can be generated from the content model via tooling; keeping handler types in sync is a separate step.TypeScript types are generated for content types; handler type safety depends on wiring those into your service.Strong TypeScript support with generated types from collections, tightly integrated because config and code share a codebase.

Ready to try Sanity?

See how Sanity can transform your enterprise content operations.