tags.json reference
src/content/tags.json holds the site’s section tags: one site-wide list of tags, and which sections carry each one. A section can carry several tags, and a tag can be on sections across any number of pages. Viewers never see tag names; tags are how editors group related sections.
Like nav.json, it is a sidecar, deliberately not a key in index.json or in a section file, and it is optional: a site without one simply has no tags. The reasons are the same as nav’s, covered in Why a sidecar.
File shape
Section titled “File shape”{ "tags": [ { "id": "tag_3f2a…", "label": "Logo usage", "sectionIds": ["logo-hero", "clearspace"] }, { "id": "tag_91c0…", "label": "Colour", "sectionIds": [] } ]}tags is the registry, in the order the editor lists it.
| Field | Type | Default | Notes |
|---|---|---|---|
id | string | — | Required, non-empty, unique. The tag’s stable identity — renaming the tag does not change it. |
label | string | "" | Display name. May be blank; the UI then shows “Untitled tag”. |
sectionIds | string[] | [] | The sections carrying this tag: ids from index.json’s sections. Order is not meaningful. |
Each tag owns its assignments, so deleting a tag removes it from every section by construction. A tag with no sections is still a registry entry, not an error.
Entries are parsed one at a time, and only a missing or empty id makes an entry invalid. A bad label reads as blank, and a sectionIds value that is not an array of non-empty strings is repaired: non-string and empty ids are dropped. An invalid entry is dropped on its own, and the read is marked degraded with a warning. A degraded file is read-only (see Writing it), which is why everything except id repairs instead of rejecting.
Source of truth: TagsFileSchema in packages/primitives/src/schemas/tags.ts.
Reconciliation
Section titled “Reconciliation”tags.json is repaired against the current index on every read by reconcileTags() (packages/primitives/src/lib/tags.ts), which is total, pure, and idempotent:
- An assignment to a section that no longer exists is dropped. This is how deleting a section removes its tags; nothing has to edit
tags.jsonat delete time. - A section id listed twice in one tag is kept once.
- A duplicate tag
idkeeps only the first tag. - A tag with no usable
idis dropped.
Read tags through siteTags(index), which always reconciles. pnpm exec authoring validate reports a file that does not parse, including one with a tag that has no id, as an error. It reports assignments to sections that no longer exist, and duplicate tag ids, as warnings.
Writing it
Section titled “Writing it”Writes follow nav.json’s rules: they are wholesale replacements.
- Leave
tagsout →tags.jsonis untouched. - Send
tags→ the file is replaced, after reconciling against the index being written.tags: []deletes every tag on a site that has the file. On a site that has never had one, an empty list creates no file. - A payload that is not a valid tag list is declined, not applied: the rest of the write lands, the file is left as it was, and the response carries a warning.
- If the file on the branch holds entries this package version cannot parse (written by a newer version, or corrupt), it is read-only: the write leaves it alone and warns, rather than replacing it with the subset this version understood.
On the wire, tags travel as a tags key on the posted index, alongside nav. The write path lifts the key off before the index is validated, so it can never reach index.json.
Through the editor
Section titled “Through the editor”- Tagging a section: every section’s chrome has a Tags pill next to the audience pill. It lists the registry as checkboxes, with a field for creating a tag inline. Typing the name of an existing tag reuses that tag.
- Managing the registry: Site Settings → Tags adds, renames, and deletes tags. Adding a name that is already in use is refused, and a rename that collides is flagged. Deleting a tag that sections carry asks for confirmation first; the sections themselves stay. An unused tag is deleted straight away.
Container children cannot be tagged; tag the container.
When two editors are working at once, a save that has to rebase onto a peer’s commit merges tags rather than replacing them. Your unsaved tag and untag actions, creates, renames, and deletes are replayed onto the tags the peer saved. A tag the peer deleted stays deleted.
If an editing session never loaded the registry (local state left by an editor build that predates tags), tag editing stays off until a reload reads the sidecar. Editing an unloaded registry would replace the real file.
Through the MCP connector
Section titled “Through the MCP connector”Reads that return the index (get_site_index, get_site_content) carry the registry as index.tags; compare_branches reports tagsChanged when the draft’s tags.json differs from main’s. save_sections writes it two ways, both wholesale under the rules above:
- a top-level
tagsparam — the full registry on its own, combinable withindexOpsorsections, so tagging never needs a full index; - a
tagskey on a posted fullindex.
Sending both is refused. Omit both and tags.json is untouched: indexOps never write tags, and the index the connector reads for itself is posted without them. Build the list from a fresh get_site_index read, and never from an incomplete read — one that returned missing, navDegraded: true or tagsDegraded: true. tagsDegraded means the file holds entries the connector can’t read, so a tags write is declined (with a warning) anyway. The index the connector reads for itself, when a save omits index, is posted without nav as well as without tags.
save_sections also checks every reference section in the payload against the tags and index the write leaves behind, and fails with [validation] (writing nothing) when a Reference names a tag that does not exist, picks a section that is not top-level on a page, picks another Reference, or picks itself. That is stricter than the viewer on purpose: the agent has just chosen those ids, so a bad one is a mistake, not a deleted source. To tag sections and reference the tag in one step, create the tag in tags in the same call. validate_site and pnpm exec authoring validate run the same check over committed content and report what they find as warnings, since the site still renders.
Related
Section titled “Related”- nav.json reference — the sidebar sidecar this file’s rules are modelled on.
- Content model —
index.json, per-section files, and how content is assembled.