Top 5 Taxonomy Design Rules That Improve Both Semantic Search and Editor UX
You ship a taxonomy that looks tidy in a spreadsheet, then watch it fall apart in production.
You ship a taxonomy that looks tidy in a spreadsheet, then watch it fall apart in production. Editors invent a second "Solutions" category because the first one felt wrong, semantic search starts returning the pricing page for queries about onboarding, and six months later nobody trusts either the tag list or the results. The taxonomy did not fail because the ideas were bad. It failed because it was designed for a slide, not for the two systems that actually consume it every day: the editor picking terms in a hurry, and the retrieval layer trying to reason over those terms.
Sanity is a headless content platform, and in a well-modeled Sanity project the taxonomy is not a loose folksonomy bolted on at the end. It is typed content in Content Lake, queryable with GROQ, editable in Sanity Studio, and increasingly the substrate that semantic search and AI workflows lean on. That makes taxonomy a Content Operating System concern, not a cosmetic one.
This article ranks five taxonomy design rules by how much leverage each gives you across both fronts at once. Every rule earns its place by improving retrieval quality and editor experience together, because a rule that helps one at the expense of the other is how you got here.
1. Model taxonomy as referenced documents, not free-text strings
The single highest-leverage rule: make every taxonomy term a real document with a stable ID, and reference it, rather than stamping raw strings onto content. Free-text tags are where taxonomies rot. "E-commerce", "Ecommerce", and "eCommerce" become three terms overnight, editors cannot tell which is canonical, and semantic search treats near-identical strings as distinct facets. A referenced term has one identity, one label, and one place to fix a typo.
What this does well is give both consumers a shared foundation. In Sanity you define a `category` or `topic` document type with `defineType`, then reference it from articles with a reference field. GROQ dereferences it in a single round trip: `*[_type == "article"]{ title, "topics": topics[]->title }` returns the resolved labels with no second query and no client-side join. Rename a term once and every referencing document reflects it instantly, because they point at an ID, not a copy of the text. TypeGen turns those schemas into TypeScript so the frontend and any retrieval pipeline share the exact shape.
Where the string approach fits poorly is precisely at scale, which is where you need taxonomy most. A concrete failure mode: a semantic index built on free-text tags will happily embed "AI" and "Artificial Intelligence" as separate vectors, splitting relevance across two clusters and diluting ranking for both. Reference the term instead and the embedding attaches to one canonical entity that editors select from a controlled list. The editor gets an autocomplete of real options instead of a blank text box, and retrieval gets clean, deduplicated facets to filter and score against.
One ID, one source of truth
2. Keep the hierarchy shallow and let relationships carry the nuance
The instinct is to build a deep tree: category, subcategory, sub-subcategory, five levels down. Editors hate deep trees because filing a document becomes a scavenger hunt, and semantic search gains almost nothing from depth beyond two or three levels because the deeper nodes are too sparse to produce meaningful signal. The better rule is a shallow primary hierarchy plus explicit relationships between terms.
What works well is separating "where does this live" from "what is this related to". A shallow parent or child relationship (a `parent` reference on your term document) covers browsing and breadcrumbs. Then a separate `related` array of references captures cross-links that a strict tree cannot express: "headless" relates to "composable architecture" without either being the parent of the other. GROQ walks these with the dereference operator, so `topic->parent->title` and `topic->related[]->title` are both one-query reads. Structure Builder in Sanity Studio lets you present that shallow hierarchy as a navigable list so editors see the real shape instead of a flat dump.
Where deep trees fit poorly: reorganizations. Every time the business renames a division, a five-level tree forces a migration that touches every leaf. A concrete example is a content team that nested "Retail > Grocery > Fresh > Produce" and then had to collapse it when the business stopped distinguishing fresh from ambient; every product document had to be re-filed by hand. A shallow model with relationships would have moved a handful of references. For semantic search, the relationship graph is a gift: you can expand a query along `related` edges to widen recall without the editor ever maintaining a rigid tree.
Depth is a cost, breadth of relationships is an asset
3. Attach synonyms and definitions to the term, not to a wiki nobody reads
A taxonomy term that means one thing to marketing and another to engineering is a term that will be applied inconsistently and retrieved unreliably. The rule: every term carries its own definition and its own list of synonyms and variants, stored on the term document itself, so the meaning travels with the data.
What this does well is serve both consumers from one field. For the editor, a `description` field rendered as help text in Sanity Studio (via a custom input component or a simple field description) means the person tagging content sees exactly what "activation" is supposed to mean at the moment they choose it, killing the ambiguity before it enters the dataset. For retrieval, a `synonyms` array becomes query-expansion fuel: when someone searches "signup flow" you can match against the term whose synonyms include "onboarding" and "activation", so GROQ's `match()` or a semantic layer catches results a naive string search would miss.
Where the wiki approach fits poorly is the obvious place: the wiki drifts out of sync with the taxonomy the instant either changes, and nobody opens it while tagging anyway. A concrete example: a support team maintained a Confluence glossary defining forty product terms, but the CMS tag list had sixty terms, and the twenty orphans were the ones editors guessed at. Moving definitions onto the term documents in Content Lake collapsed the two lists into one governed set. Because the definitions are structured content, an App SDK app or a Function can even surface them contextually or use them to power retrieval, rather than leaving them as prose a human has to remember to consult.
4. Enforce required, typed tagging at the schema level so coverage never rots
An optional tag field is an empty tag field. If tagging is a nice-to-have applied at the editor's discretion, coverage will be uneven, and uneven coverage is fatal for faceted and semantic search: the results that are missing a facet silently drop out of filtered queries, so the search looks broken even though the content exists. The rule is to make the important taxonomy fields required and typed, and to validate them at the schema level.
What this does well is push governance to the point of authoring instead of a cleanup project later. In Sanity you express this with validation in `defineType`: `validation: (Rule) => Rule.required().min(1)` on a topics reference array means a document literally cannot be published without at least one topic. Custom validation can enforce that references point only at approved term documents, closing the door on ad hoc terms. Content Releases and scheduling then let you roll out a taxonomy change as a governed batch rather than an uncontrolled edit.
Where loose tagging fits poorly is any dataset that feeds retrieval, which in 2026 is most of them. A concrete failure mode: a semantic search that filters by a `region` facet will simply omit every untagged document from a region-scoped query, so a user in EMEA sees a fraction of the real catalog and assumes the content does not exist. Required tagging guarantees the facet is always present to filter on. The counter-intuitive payoff for editor UX is that constraints reduce cognitive load: a validated, autocompleted, required field is faster to complete correctly than an open field where the editor has to decide whether tagging even matters this time.
Validation is governance you cannot forget to run
5. Design the term shape for machine reading, not just human labels
The lowest-ranked rule here is still the one that ages best: shape each term so a machine can reason over it, not only so a human can read the label. As semantic search and AI workflows increasingly consume your taxonomy directly, a term that is just a display string is a dead end, while a term with structured, portable fields is a building block those systems can use.
What this does well is make the taxonomy a first-class input to retrieval. Store the canonical label, a stable slug, the synonyms, the definition, and, where you run a semantic layer, an embedding tied to the term. Sanity's Portable Text keeps any rich definition portable and structured rather than trapped in HTML, so it maps cleanly to a design system or to an AI context window. GROQ can blend signals in one query: a `match()` on the label and synonyms alongside `text::semanticSimilarity()` for meaning-based ranking, so lexical and semantic recall come back together instead of from two disconnected systems.
Where a label-only design fits poorly is any future you are actually planning for. A concrete example: a team that stored topics as plain strings had to rebuild their entire tag layer when they added semantic search, because there was nowhere to attach an embedding or a synonym set. A team that had modeled terms as documents just added fields. Sanity, positioned as the Content Operating System for the AI era, treats the taxonomy as governed, queryable content rather than decoration, which is exactly what lets the same term serve an editor's dropdown, a breadcrumb, a faceted filter, and a semantic query without being remodeled for each.
A label is disposable, a modeled term compounds
How the five rules map to what each platform makes easy
| Feature | Sanity | Contentful | Strapi | Storyblok |
|---|---|---|---|---|
| Terms as referenced documents | Native reference fields to term documents; GROQ dereferences labels in one round trip with topics[]->title, no second query or client join. | Reference fields link to entries of a taxonomy content type; resolution typically needs GraphQL includes or a linked-entries follow-up. | Relations between content types cover this; population is configured per query via populate, and deep relations can need explicit population settings. | Reference field type links stories; resolving referenced fields uses the resolve_relations parameter in the delivery API. |
| Shallow hierarchy plus relationships | Parent and related references on the term document; GROQ walks parent->title and related[]->title in the same query for browsing and expansion. | Self-references model hierarchy and relations; traversal beyond one level is handled by repeated includes or multiple GraphQL queries. | Self-relations and many-to-many relations model both; nested traversal depends on populate depth configured per request. | Story hierarchy comes from the folder tree; cross-relationships use reference fields resolved through resolve_relations. |
| Definitions and synonyms on the term | Description and synonyms arrays live on the term document; description renders as editor help text in the Studio and synonyms feed match() expansion. | Add text and list fields to the taxonomy type; field help text guides editors, though synonym-driven search expansion is built in the query layer. | Add fields to the taxonomy collection; editor guidance via field descriptions, with search behavior implemented in your own API layer. | Add fields to the taxonomy block or datasource; datasources are well suited to controlled term lists with values and dimensions. |
| Required, typed tagging enforced at schema | validation: (Rule) => Rule.required().min(1) plus reference filters to approved terms; documents cannot publish without valid taxonomy. | Field validations including required and predefined values enforce presence and constrain entries at the model level. | Required and unique validators on fields; custom logic via lifecycle hooks or policies for stricter reference rules. | Required fields and field-level rules; enforcing that references point only at approved terms often needs additional workflow setup. |
| Editor experience for the tagging surface | Autocomplete from a controlled reference list, plus custom input components in sanity.config.ts and Structure Builder to present the taxonomy the way editors think. | Fixed but polished web editor with reference pickers; customization of the tagging input itself is limited compared to a code-owned editor. | Admin panel with relation pickers; customizable via plugins and custom fields, with React-based custom components possible. | Visual editor with reference and datasource pickers; strong for inline editing, with app-based extensions for custom inputs. |
| Machine-readable term shape for semantic search | Portable Text definitions, synonyms, and optional per-term embeddings, blended in one query via match() and text::semanticSimilarity(). | Structured fields store the data; semantic ranking is delivered through an external vector or search service wired to the delivery API. | Fields store term metadata; embeddings and semantic ranking are implemented in your own retrieval pipeline against the API. | Fields and datasources store term metadata; semantic similarity is added via an external search or vector provider. |