How to Generate and Validate Structured SEO Metadata in a CMS
A product page ships with an empty meta description, a title that runs to 90 characters and gets truncated in the SERP, and a canonical tag that quietly points at a staging URL.
A product page ships with an empty meta description, a title that runs to 90 characters and gets truncated in the SERP, and a canonical tag that quietly points at a staging URL. Nobody notices until organic traffic dips and a crawl report surfaces four hundred documents with the same problem. The failure is not that anyone was careless. It is that SEO metadata usually lives as free text bolted onto content, unvalidated at authoring time and invisible in aggregate, so mistakes accumulate silently across a whole content set. Sanity treats this differently. As the Content Operating System for the AI era, an intelligent backend for teams running content operations at scale, it turns metadata from loose strings into modeled, validated, queryable fields. This article reframes SEO metadata as a data-modeling and governance problem rather than a copywriting afterthought. We will walk through modeling metadata as discrete fields, generating it from your existing structured content, validating it both at the field level and across the entire dataset in a single query, and delivering it to every channel. The through-line is that once metadata is real structured content, the whole class of silent-drift bugs becomes something you can catch before publish, not something you discover in a quarterly audit.
Why free-text metadata drifts, and what modeling fixes
Most SEO metadata problems are structural, not human. When a title tag, meta description, canonical URL, Open Graph image, and robots directives are stored as an untyped blob or a handful of loose strings, nothing enforces a length ceiling, nothing flags a missing description, and nothing prevents two documents from claiming the same canonical. Each field is a place a mistake can hide, and because the fields carry no rules, the mistakes only show up downstream in a crawler report or a ranking drop. The fix is to model each piece of metadata as a discrete field with its own type, its own constraints, and its own owner. In Sanity you do this with defineField and defineType, declaring metaTitle as a string, metaDescription as a string, canonical as a URL, ogImage as an image, and robots as a set of explicit options. Splitting metadata into fields this way is not cosmetics. As the Sanity knowledge base puts it about structured content generally, splitting a thing into fields is access control: different fields can be owned by different teams, validated independently, and each document carries version history, scheduled publishing, and rollback in the Studio. That last point matters more than it sounds. When a canonical gets pointed at the wrong URL, you want to see who changed it, when, and roll it back, not reconstruct the incident from a diff of production HTML. Modeling metadata as governed fields is the difference between a system that prevents the empty-description bug and one that merely records it after the fact. This is the first pillar, model your business, applied to the least glamorous but highest-leverage surface in your content: the fields that decide how every page is represented to search engines and social platforms.
Field-level validation: catching the bug at the keystroke
The cheapest place to catch a bad meta description is the moment an editor types it, not a crawl three weeks later. Field-level validation is how you move the check to the keystroke. A title tag longer than roughly sixty characters gets truncated in most search results, so the field should warn or block past that ceiling. A meta description has a practical sweet spot around 150 to 160 characters, so the field should surface a live character count and flag both the empty case and the overlong one. A canonical URL should be a well-formed absolute URL, not a relative path or a staging host. In Sanity Studio these are ordinary schema rules: Rule.required() to make a description mandatory, Rule.max(60) to cap a title, Rule.uri() to constrain a canonical to a valid absolute URL, and a custom async validation function to check that no other published document already claims the same canonical. Because the Studio is a React application you ship rather than a fixed UI you rent, you can go further than a warning string. You can build a custom input component that renders a live SERP preview beside the fields, shows the pixel-width truncation point instead of a raw character count, and turns the abstract rule into something an editor actually understands. The validation runs where the writing happens, so the person best placed to fix the problem sees it while the context is still in their head. Contrast this with the common pattern of validating metadata in a separate SEO tool that only sees the rendered page. By then the author has moved on, the fix requires a round trip, and the incentive to leave it broken is high. TypeGen closes the loop for developers by generating TypeScript types from your schema, so the frontend that renders these tags gets typed access to every metadata field and a build breaks if a field is renamed or removed. Field-level validation is the automate-everything pillar at its most local: the check is part of the model, so it is impossible to skip.
Generating metadata from the content you already have
Writing metadata by hand for hundreds of documents is where the empty-description problem is born. Nobody skips the description because they are lazy. They skip it because they are staring at document number two hundred and the marginal value of one more hand-crafted summary feels like zero. The answer is to generate a strong first draft from the structured content that already exists on the document, then let a human approve or refine it. Generation only works well when it is grounded in real fields rather than a guess, and this is exactly where modeled content pays off a second time: a title, a body in Portable Text, a product category, and a price are all discrete, machine-readable inputs an LLM can summarize faithfully instead of paraphrasing from rendered HTML. Sanity's Agent Actions, now the Agent API, are schema-aware APIs for generating, transforming, and translating content with LLMs, exposed over HTTP anywhere you can run code. Because they are schema-aware, a generation call knows the shape of your metaDescription field, its length constraint, and the source fields it should draw from, so the output lands as valid content in the right field rather than a blob you have to parse. Sanity also publicly ships an SEO for AI guide framed explicitly around using Content Lake to feed AI assistants, which is the same grounding mechanism pointed at metadata: structured content in, faithful summaries out. A useful pattern is a Function that runs on publish, calls the Agent API to draft any missing metaTitle or metaDescription, and stages the result in a draft for review rather than pushing it live unattended. The knowledge base is blunt about why grounding matters: a tool that returns prose forces the model to paraphrase, and paraphrasing is where facts go to die. Metadata generated from your own structured fields sidesteps that failure because the model is summarizing data, not inventing it. This is the automate-everything pillar scaling output instead of scaling headcount: the same team ships complete, on-spec metadata across the entire catalog.
Dataset-wide validation in a single query
Field-level rules stop new mistakes. They do nothing about the four hundred documents that were created before you added the rules, or the ones an import script populated with nulls. For that you need to ask a question of the whole dataset at once: which published documents have no meta description, which titles exceed sixty characters, and which canonical URLs are claimed by more than one document. In most stacks that means exporting content, running a script, and reconciling the results against production, all of which drift out of date the moment someone publishes. GROQ collapses the audit into one round trip against Content Lake. You write a projection that filters for the failure conditions and returns exactly the fields you need to fix them, for example every product where metaDescription is null or where length of metaTitle is greater than sixty, ordered so the worst offenders surface first. The same query surface that powers hybrid retrieval, blending structured predicates for the filters that have to hold with score() over boost([field] match text::query()) and text::semanticSimilarity() for ranking, is the surface you use to build validation and audit queries across a whole dataset in one query. The result reflects current published state by construction, because Content Lake keeps its index fresh: when a description updates, a field changes, or a document is deleted, the index knows. The knowledge base is specific about why that matters. Building incremental indexing, re-embedding on change, deletion handling, and backfill yourself is a real project and a class of bug all its own. When the audit query always sees the true current state, the audit stops being a snapshot you distrust and becomes a live health check you can run in CI. Pair the query with Content Releases to batch the fixes into a single governed, scheduled publish rather than four hundred one-off edits.
Delivering validated metadata to every channel
Metadata that is correct in the Studio is worthless if it renders inconsistently across your website, your app, your syndication feeds, and the previews that show up in Slack or on social. The delivery problem is where silos re-enter: a title stored in the CMS, an Open Graph image hard-coded in the frontend, and a schema.org JSON-LD block generated by a separate build step will eventually disagree with each other. The fix is a single source of truth queried the same way everywhere. Because metadata lives as structured fields in Content Lake, one GROQ query returns the metaTitle, metaDescription, canonical, ogImage, and robots directives for a page in exactly the shape the frontend needs, in one round trip, with references resolved through the -> operator so an inherited category description or a shared default comes back already joined. The same query feeds a Next.js generateMetadata function, an RSS builder, and a JSON-LD generator without three separate integrations drifting apart. Portable Text keeps richer descriptive content portable and mappable across those channels rather than trapped in channel-specific markup. Visual Editing and the Presentation Tool let an editor see the rendered title, description, and social card against the live page without leaving the editorial context or giving up the headless architecture, so the preview an author approves is the artifact that ships. This is the power-anything pillar: model the metadata once, validate it once, then deliver it to every surface from the same governed store. The end-to-end reach is the point. Legacy CMSes stop at publishing and hand the delivery problem to your engineers; here the same structured fields flow from authoring through validation to every channel that needs them.
Governance, audit, and the compliance surface
SEO metadata is customer-facing behavior, and it is worth governing like it. A rogue robots directive can deindex a section of the site. A canonical pointed at a competitor or a staging host can leak ranking authority. These are not typos, they are incidents, and the difference between a well-run content operation and a fragile one is whether such a change is versioned, reviewed, validated, and gated before it ships. The knowledge base frames the real choice precisely: not content loose versus code rigorous, but governed, with the right people able to edit and a test gate on the way out, versus a string only engineering can touch. Metadata in the Studio gets the governed side of that trade by default. Every field change carries version history and rollback, Roles and Permissions decide who can edit sensitive fields like robots and canonical, Content Releases stage batched changes for review, and Audit logs record who changed what and when. You can wire a validation query into CI as the test gate, so a release that would introduce a duplicate canonical or a missing description fails the check before it reaches production, the same way author it like content, gate it like code reads for any high-stakes content surface. On the platform compliance side, Sanity maintains SOC 2 Type II, supports GDPR, offers regional hosting and data residency, and publishes its sub-processor list, which matters when metadata generation routes content through model providers and you need to know where data flows. Treating metadata as governed content rather than an unversioned string is what turns a class of quiet, expensive SEO incidents into changes you can see, review, and undo.
Generating and validating SEO metadata: Sanity vs Contentful, Strapi, and Payload
| Feature | Sanity | Contentful | Strapi | Payload |
|---|---|---|---|---|
| Metadata schema definition | Code-first defineField/defineType you version in git; metaTitle, canonical, ogImage, and robots modeled as discrete typed fields. | Content types with SEO fields, but schemas are managed in the web UI or CLI rather than as versioned code you own. | Community SEO plugin adds meta fields; content types are configurable, defined through the admin UI and config. | Config-as-code in TypeScript with an official SEO plugin for meta title, description, and OG fields; a real code-first peer. |
| Field-level validation | Rule.required(), Rule.max(60), Rule.uri(), plus custom async checks for duplicate canonicals, enforced at authoring time in the Studio. | Field-level validation within a fixed editorial UI; rules configured per field in predefined slots. | Validation available per field; custom rules and duplicate checks are yours to configure or build in the plugin layer. | Field validation defined in the collection config; custom hooks handle bespoke checks like duplicate canonicals. |
| Custom editor for SEO inputs | Studio is a React app you ship: build a live SERP preview and pixel-width truncation input as custom components. | Editor is a fixed layout extended via UI extensions in predefined slots, not a fully code-owned editor. | Admin UI is customizable to a degree via plugins, but it is not a code-first editor you rebuild. | Admin UI supports custom React field components, giving strong control over bespoke SEO inputs. |
| AI-assisted metadata generation | Agent API: schema-aware generation grounded in your structured fields, over HTTP, so output lands as valid field content. | Generation via marketplace AI apps or your own integration; grounding in structured fields is wiring you assemble. | Typically means installing or building a plugin, or wiring an LLM pipeline yourself, then operating it. | No first-party schema-aware generation API; you integrate a model provider and ground it against your own data. |
| Dataset-wide validation audit | Single GROQ query against Content Lake finds every null description, overlong title, or duplicate canonical, reflecting current published state. | Query via GraphQL or Content Delivery API; aggregate audits typically combine multiple calls or an external crawl. | REST or GraphQL queries; dataset-wide audits are scripts you write and keep in sync with the data. | Query via Payload's own REST or GraphQL layer; cross-collection audits are queries and scripts you own. |
| Delivery to every channel | One GROQ projection with -> reference resolution returns metadata in the exact shape the frontend, feeds, and JSON-LD need. | Delivers metadata via CDN and GraphQL; joining referenced defaults means shaping data across multiple queries or client code. | Delivers via REST and GraphQL; shaping and joining for multiple channels is handled in your application layer. | Delivers via REST and GraphQL with depth-based population; projections and blended queries work differently from GROQ. |