Accepted

ADR 0029 — Collection and document slug patterns

Collection and field slugs are snake_case identifiers; document slugs are kebab-case URL segments

Decision

Collection slugs and document slugs follow different rules, and the difference is kept legible everywhere an editor encounters it. A collection slug is a snake_case identifier — lowercase, starting with a letter, with only letters, digits, and underscores (`^[a-z][a-z0-9_]*$`). A document slug is a kebab-case URL segment: the web convention of lowercase words joined by hyphens.

They look the same to an editor — both lowercase, both naming a thing — so without explanation the divergence reads as a bug. The fix is not to unify them but to explain why they differ in the same sentence the editor reads when typing one.

Why they differ

They are different things. A collection slug is an identifier: it appears unquoted as the table suffix of generated SQL identifiers in the headless query builder, as a segment of every schema cache key, as a token segment in the API-token scope grammar, and as the input to the PascalCase conversion that produces generated TypeScript type names. Hyphens break bare-identifier safety in SQL and the PascalCase round-trip, so the identifier pattern forbids them.

A document slug is a URL segment. The web convention is hyphens for readability and SEO; underscores in URLs are legacy and harder to read. Every seeded document uses hyphens — cuthberts-spelt-pancake, submit-a-sighting, geralds-drop-scones — and that is correct.

Field slugs follow the same snake_case identifier pattern as collection slugs, because a field slug plays the same identifier role.

Enforcement

The collection-slug identifier rule is enforced in code at both the API gate and the admin SPA router, which agree on the same pattern. There is no database CHECK constraint and no data migration: no collection slug uses a hyphen, and the invariant lives in code.

The admin tells the editor both the rule and the reason in one place. Typing a hyphenated collection slug surfaces: use lowercase letters, numbers, and underscores (no hyphens) — collection slugs are identifiers, not URL words; document slugs are different and use hyphens. The slug field hint adds that the slug is used in URLs, the API, and as the table name in the generated TypeScript schema, and that it cannot be changed later.

Consequences

  • An editor who types a hyphenated collection slug reads the rule, the reason, and the location of the other rule in a single sentence — no trial and error.
  • The headless query builder, scope grammar, schema cache keys, and TypeScript codegen all rely on the snake_case-only invariant for collection and field slugs.
  • Document slugs stay kebab-case for clean, readable, SEO-friendly URLs.
Read the collections and field definitions decision