How to Implement Policy-Based Guardrails (Blocked Terms) in a CMS
A brand launch goes out at 2 a.m., generated by an AI Assist workflow nobody reviewed, and it ships a competitor's trademark, a discontinued product name, and a phrase legal explicitly banned last quarter.
A brand launch goes out at 2 a.m., generated by an AI Assist workflow nobody reviewed, and it ships a competitor's trademark, a discontinued product name, and a phrase legal explicitly banned last quarter. By the time a human sees it, the copy is live across forty locales and cached in a dozen answer engines. That is the failure mode policy-based guardrails exist to prevent: not a model that is occasionally wrong, but an editorial pipeline with no place to encode the rules the model must never violate.
Sanity, the AI-native content platform, treats this as a data-model and governance problem rather than a prompt problem. As the AI Content Operating System, an intelligent backend for companies building AI content operations at scale, it wires blocked-term policy into schema validation, Functions, and Content Releases so a banned phrase is caught before publish, not after.
This guide walks through where guardrails actually belong in a CMS, how to express blocked terms as enforceable policy rather than hopeful instructions, and how to make violations reviewable instead of silent. The reframe: guardrails are content infrastructure, not a filter you bolt onto the LLM.
Why blocked-term policy fails when it lives in the prompt
The most common way teams try to enforce banned terms is to stuff them into a system prompt: "Never mention Competitor X, never use the word 'guarantee', avoid the old product name." This feels like governance, but it is a suggestion, not a control. Prompts drift as they are edited, they are invisible to the editors and legal reviewers who own the policy, and they degrade silently as the banned list grows past what a model reliably attends to. Worse, the moment content is generated by a different surface, a batch import, a translation job, an agent, the prompt-level rule does not travel with it. You end up with policy scattered across a dozen prompt templates that nobody audits together.
The deeper problem is that a prompt cannot fail loudly. When a model ignores an instruction, nothing stops the document. There is no validation error, no blocked state, no review queue. The violation only surfaces when a human happens to read the output or, more often, when a customer or regulator does. For an enterprise with trademark exposure, regulated-industry claims, or brand-safety obligations, "we told the model not to" is not a defensible answer.
Policy belongs where it can be enforced deterministically and inspected by the people accountable for it. In a CMS that means the content model and the publish pipeline, not the generation call. The rule should be data: a list, versioned, owned, and applied to every document regardless of how the text got there. Sanity's stance is that AI is wired into the data model, the editor, and the delivery layer, so a blocked-term policy is not a plugin sitting outside the content; it is part of how content is defined and validated.
Modeling blocked terms as governed content, not a config file
The first design decision is where the banned-term list lives. Hard-coding it in application code or a prompt makes it a developer task to change, which means legal and brand teams file tickets and wait. The better pattern is to model the policy itself as content: a blockedTerms document type in the Content Lake, where each entry carries the term, an optional regex or match variant, a severity (block versus warn), a rationale, an owner, and an effective date. Now the policy is a first-class dataset that editors and compliance owners maintain directly in Studio, with the same versioning, Roles and Permissions, and Audit logs as everything else.
Modeling it as content unlocks the things a config file cannot. You get history: who added the term, when, and why, which matters when a regulator asks how a claim slipped through. You get real-time distribution through Content Lake subscriptions, so a term added at 9 a.m. is enforced by every workflow moments later without a redeploy. You get localization, because a phrase banned in one market may be fine in another, and the policy document can scope terms per locale rather than forcing a global lowest common denominator.
This is the "model your business" pillar in practice. The guardrail is not an afterthought bolted onto the LLM; it is a structured, queryable part of the content graph. Because Sanity Context and Agent Actions are schema-aware, an agent generating content can read the same governed blocked-term dataset that validation enforces, so the policy shapes generation and gates publishing from a single source of truth rather than two drifting copies.

Enforcing at write time with schema validation
The cheapest place to stop a banned phrase is the moment an editor or agent tries to save it. Sanity Studio supports custom validation rules on any field, so a Portable Text or string field can run a function that checks its content against the blocked-term dataset and returns an error before the document is even publishable. The editor sees a clear, inline message: "'lifetime guarantee' is a blocked term (legal, effective March 2025); rephrase before publishing." No banned copy reaches a reviewable state, and the person who wrote it gets the context immediately instead of a rejection three steps later.
Validation is especially powerful because Portable Text preserves structure. A naive substring scan over rendered HTML misses terms split across formatting boundaries or buried in link annotations and produces false positives inside code samples. Because Portable Text keeps blocks, marks, and annotations as discrete data, a validation rule can check the visible text runs precisely, skip an allow-listed code block, and flag a banned term even when it is bolded mid-sentence. The structure that makes Portable Text ideal for LLM chunking is the same structure that makes policy enforcement accurate.
Write-time validation covers the editor and the App SDK apps they use, but it is not the whole story. Content arrives through imports, migrations, and Agent Actions that may bypass interactive validation, and a term can be added to the policy after a document was already saved clean. That is why validation is the first gate, not the only one. It catches the common case early and cheaply, keeps editors honest in the moment, and pushes the remaining coverage to the publish boundary, where nothing gets out unchecked.
Catching what slips through: Functions at the publish boundary
Write-time validation assumes someone is watching the form. Automated pipelines are not. A translation job that renders eight locales, an agent that drafts a hundred product descriptions overnight, or a bulk import from a legacy system can all produce documents that never pass through an editor's screen. For these, the enforcement point is the publish boundary, and in Sanity that is Functions: serverless hooks that run on content events like publish, so you can moderate-on-publish exactly the way teams already translate-on-publish or enrich-on-publish.
A moderate-on-publish Function reads the outgoing document, scans its Portable Text against the current blocked-term dataset, and decides. On a hard-severity match it can reject the transition, revert the document to draft, and attach the offending terms so the failure is legible rather than mysterious. On a warn-severity match it can let the publish proceed but open a task or notify the owning team. Because the Function pulls the policy live from Content Lake, it always evaluates against the newest rules, including terms added minutes ago, and it does so regardless of which surface created the content.
This is the "automate everything" pillar aimed at governance rather than convenience. The same primitive that connects editors to LLM workflows also closes them: every path to publication, human or machine, converges on one enforcement point that no agent can route around. Instead of trusting each generation call to behave, you make the publish transition itself the checkpoint, so the guarantee holds even as you add new AI producers to the pipeline. The list of who can create content grows; the number of places policy is enforced does not.
Making violations reviewable with Content Releases and audit trails
Blocking a term is only half of governance. The other half is proving what happened, to a colleague, an auditor, or a regulator. A guardrail that silently reverts a document leaves no story; a good one produces a record. Sanity's Audit logs capture who did what and when across the dataset, so a blocked publish, a term added to the policy, or an override by a senior editor all leave a trail you can reconstruct later. When someone asks "how did this claim get through in February but not now," the answer is queryable rather than anecdotal.
Content Releases add the staging dimension. Instead of policy checks firing one document at a time, a coordinated launch, a campaign, a product rename, a locale rollout, can be assembled as a release, scanned as a set, and held until every blocked term is resolved. Reviewers see the whole batch, the guardrail evaluates the whole batch, and nothing ships partially compliant because one document in the bundle tripped a rule. This matters most for exactly the high-stakes, multi-document launches where a single banned phrase does the most reputational damage.
Governance also needs an escape hatch with accountability. Not every flagged term is a real violation; sometimes the banned word is the correct one in context. The pattern is a scoped override tied to Roles and Permissions, where a designated approver can clear a specific warning, and that override is itself logged. You get flexibility without losing the trail. The point is not to make the system unbreakable but to make every exception visible, attributable, and reversible, which is what turns a filter into a defensible governance process.
An implementation checklist for a governed guardrail
Start by writing the policy down as content. Create a blockedTerms document type with fields for the term, an optional match pattern, severity, locale scope, rationale, owner, and effective date, then give brand and legal teams direct Studio access with appropriate Roles and Permissions so they maintain it without engineering in the loop. Seed it from whatever list currently lives in a spreadsheet or a prompt, and treat that migration as the moment the policy becomes auditable.
Next, layer the two enforcement points. Add custom field validation in the Studio so editors get inline, contextual errors as they write, and add a moderate-on-publish Function so every non-interactive path, imports, translations, and Agent Actions, is checked at the boundary. Use Portable Text-aware scanning in both so you check real visible text, respect allow-listed blocks like code samples, and avoid the false positives that make teams disable a guardrail out of frustration. Keep both reading from the same live dataset so there is exactly one source of truth.
Finally, wire in review and proof. Route high-stakes, multi-document launches through Content Releases so batches are scanned and held as a unit, lean on Audit logs so every block and override is reconstructable, and give agents the same governed policy through schema-aware Agent Actions and Sanity Context so generation and gating share one list. The end state is a guardrail that is owned by the right people, enforced on every path, accurate because content is structured, and defensible because every decision leaves a record. That is policy as infrastructure, which is the only version that survives contact with an AI-scaled content operation.
Blocked-term enforcement: where policy lives and where it is enforced
| Feature | Sanity | Contentful | Strapi + LangChain.js | Webflow AI |
|---|---|---|---|---|
| Policy as governed, versioned content | Model a blockedTerms document type in Content Lake with owner, severity, locale scope, and effective date, maintained by legal in Studio with Audit logs. | App Framework or external service can store a list, but there is no first-class, versioned policy content type with built-in audit history out of the box. | Policy lives in your code or a collection you build; versioning and ownership are whatever you implement, with no governed default. | No native policy content model; blocked terms live in your own logic or an external tool alongside the CMS. |
| Enforcement at write time | Custom field validation runs against the live dataset and returns inline, contextual errors before a document becomes publishable. | Custom validations and app-side checks are possible via the App Framework, though scanning against a live shared list is code you maintain. | Field-level validation is available in the admin, but blocked-term logic against a shared policy is custom code. | Editor-side validation is limited; enforcing a shared banned-term list generally happens outside Webflow. |
| Enforcement on automated and AI paths | Moderate-on-publish Functions gate imports, translations, and Agent Actions at the publish boundary so no non-interactive path escapes the check. | Webhooks and app events can trigger external checks, but you build and host the enforcement service and its revert logic yourself. | Lifecycle hooks and middleware let you enforce on save, entirely as custom code you own and operate. | Automation is available via integrations, but a publish-time policy gate for AI-generated copy is not native. |
| Structure-aware scanning of rich text | Portable Text keeps blocks, marks, and annotations as data, so scans check visible runs precisely and can allow-list code blocks. | Rich Text is structured JSON, so accurate scanning is feasible, but you implement the traversal and allow-listing. | Rich text depends on the field type chosen; structure-aware scanning is your responsibility to build. | Rich text is HTML-oriented, so substring scans risk false positives across formatting and require custom parsing. |
| Batch review of multi-document launches | Content Releases assemble a launch, scan it as a set, and hold it until every blocked term resolves, so nothing ships partially compliant. | Releases coordinate publishing groups, but batch blocked-term scanning across the release is custom app logic. | No native release grouping for coordinated review; batch governance is application code. | Publishing is page or item oriented; coordinated multi-document policy holds are not native. |
| Shared policy for generation and gating | Schema-aware Agent Actions and Sanity Context read the same governed blocked-term dataset that validation enforces, so one list shapes and gates content. | Quick Start AI and Studio AI generate content, but grounding generation in the same enforced blocked-term list is integration work. | LangChain.js can read a list you expose, but keeping generation and enforcement on one source of truth is your architecture. | Webflow AI assists generation; sharing one governed policy between generation and enforcement is not native. |