Collections

A collection is the combination of three things, all derived from one source of truth:

  1. Field descriptors (src/descriptors/ builders, used by each manifest) — define the content shape.
  2. An editor manifest (src/editor/manifests/<slug>.ts) — wires the fields, routes, and access predicates into the admin's /cms surface.
  3. A Convex table (packages/convex/convex/tables/cms.ts) — generated from the descriptors by pnpm cms:gen; every content table keys on the shop id with a by_shop index.
CollectionManifestNotes
pagessrc/editor/manifests/pages.tsSlug + blocks. Drafts + autosave.
articlessrc/editor/manifests/articles.tsSlug + rich-text body. Drafts.
productMetadatasrc/editor/manifests/product-metadata.tsPer-shopifyHandle SEO/marketing overrides.
collectionMetadatasrc/editor/manifests/collection-metadata.tsPer-shopifyHandle overrides for Shopify collections.
headersrc/editor/manifests/header.tsSingleton — one row per tenant.
footersrc/editor/manifests/footer.tsSingleton — one row per tenant.
businessDatasrc/editor/manifests/business-data.tsSingleton — addresses, phone, support email, etc.
mediasrc/editor/manifests/media.tsxUploads land in Convex file storage; legacy assets resolve via the S3_*/R2 fallback URLs.
userssrc/editor/manifests/users.tsEditor accounts.
shopssrc/editor/manifests/shop.tsEdit surface over the Convex shops table (name, domain, design); provider secrets never reach the editor.
reviewssrc/editor/manifests/review.tsTenant-scoped product reviews.
feature-flagssrc/editor/manifests/feature-flag.tsAdmin-only; keyed by key.

allManifests (src/editor/manifests/index.ts) is the registry the admin app mounts. The legacy tenants slug survives only as an editor-route alias rendering an empty surface (legacy-tenants-slug.ts) — shop == tenant since the Convex migration, so there is no tenants collection.

Access predicates

Editor access is declared per manifest with the predicates from src/editor/access.ts:

import { adminOnly, editorOrAdmin, tenantMember } from '@nordcom/commerce-cms/editor';
PredicateGrants
adminOnlyPlatform admins only (destructive operations, feature flags).
editorOrAdminAny signed-in editor or admin (cross-tenant list surfaces).
tenantMemberAdmins, or editors whose tenants include the route's tenant id.

These gate the editor routes; the Convex functions enforce the same boundary server-side via their tenant-aware constructors, so a forged route never reads another tenant's rows.

Revalidation

There are no collection hooks to wire — publishing a document runs the Convex revalidation pipeline (packages/convex/convex/revalidate/), which fans out the three CMS cache tags (cms.<shopId>.<collection>.<key> / cms.<shopId>.<collection> / cms.<shopId>) and delivers them to the storefront's /api/revalidate/convex route as signed events.

Adding a collection

See Adding a collection: declare the descriptors, register a manifest, run pnpm cms:gen, and commit the regenerated artifacts.

On this page