Manifest & sections
A store’s entire look and layout is data, not code. That data is the manifest: brand, theme tokens, catalog metadata, navigation, compliance settings, and an ordered list of sections. The storefront fetches the manifest when it resolves a store, and renders exactly what the manifest describes. Reordering a store, changing its palette, or adding a hero is a manifest edit — never a deploy.
This is the founding rendering decision (ADR-0003): storefronts are served by a single multi-tenant fleet renderer, and customization is data — theme tokens, a typed section registry, per-store CSS custom properties. There is no per-merchant code in the shared renderer, and there is no template language. A store that genuinely outgrows data goes headless (ADR-0008) — the only custom-code path — and reads the same manifest through the public API.
The shape of a manifest
When a store resolves (by tenant or by verified host), it receives a manifest roughly shaped like:
{
"store": { "id": "…", "slug": "…", "status": "live" },
"brand": { "name": "…", "logo": "…" },
"domains": { "primary": "…", "aliases": ["…"] },
"theme": { /* color / type / layout tokens */ },
"catalog": { "storeKey": "…" },
"template": { "sections": [ /* ordered sections */ ], "pages": [ /* policy pages */ ] },
"comms": { /* consent + compliance settings */ }
}store.statusgates everything: only alivestore serves catalog and checkout. The renderer gates public serving on the manifest’s ownstore.status, so an approved store’s go-live publishes aliveversion and the store actually serves. A non-livestore renders an “unavailable” state, not an error, and an unknown tenant is a real 404 — never an empty manifest.themecarries color, type, and layout tokens.template.sectionsis the ordered page.template.pagesare the store’s policy pages.comms/ compliance drives age gates, cookie consent, and the RUO disclaimers where applicable.
Sections — what actually renders
template.sections is an ordered list of { type, props } entries, each a
typed section:
"sections": [
{ "type": "announcement", "props": { "text": "Free shipping over $75" } },
{ "type": "header-nav", "props": { "links": [{ "label": "Shop", "href": "/shop" }] } },
{ "type": "hero", "props": { "heading": "…", "cta": { "label": "…", "href": "…" } } },
{ "type": "product-grid", "props": { "collection": "featured" } },
{ "type": "coa-block", "props": { "…": "…" } },
{ "type": "legal-footer", "props": { "…": "…" } }
]Both the fleet renderer and the merchant site-builder render these from one
source — the @dscodotco/storefront-sections package (renderSection /
renderSections). What a merchant arranges in the editor is literally the same
component, with the same prop validation, that a visitor sees. Adding or
reordering a section in the manifest re-lays-out the store with no code change;
a section type the renderer does not know is simply not rendered.
Each section type has a prop schema, and those schemas are versioned like events (ADR-0003): additive within a version, a breaking change is a new section version, and old versions serve until a store’s manifest is migrated. A manifest-validation gate runs before any renderer deploy that changes the registry — at hundreds of stores, a breaking section change would otherwise be a live-manifest migration times every store.
Theme tokens
theme is an inert token tree — colors, type, and layout — that the starter
maps onto CSS custom properties (for example --color-primary,
--color-ink, --font-display). You style components against those variables
rather than hard-coded values, so a manifest theme change reflows the whole
store. See Theming for the token conventions, the preset
matrix, and how a custom frontend consumes them.
Because tokens are inert data (unlike section prop schemas, which are code), a theme change is never a renderer migration — only a manifest edit.
Navigation
Navigation is authored as a header-nav section whose props.links is the
menu ([{ label, href }, …]). A custom frontend reads exactly that rather than
re-declaring its own nav:
const { manifest } = await store.site.get();
const nav = manifest.template?.sections?.find((s) => s.type === "header-nav");
const links = nav?.props.links ?? []; // fall back to collections only on honest absenceThe site-builder publish flow
Merchants edit the manifest through the portal’s site builder, which never writes the live store directly. The model is immutable versioned manifests with an active pointer:
- A draft is a new immutable version the active pointer does not reference — editing the draft leaves the live site untouched.
- Publish moves the active pointer to a version. Publishing an older version is how a rollback works.
- Preview mints a short-lived signed token over
(tenant, version, expiry); the storefront renders that exact draft version behind a non-dismissible “Preview — not published” banner and forcednoindex, bypassing host resolution and the ETag cache.
The fail-closed RUO-claims scan runs on the draft write
(PUT /v1/my/site/draft, and equally PUT /v1/my/store/manifest), not on
publish. The scanner walks every string in the manifest tree: a CRITICAL
finding is a hard 422 and no version row is written, and a scanner error
is a 503 (“scan did not run”), never a silent pass. The pointer move —
POST /v1/my/site/publish — does not re-scan, because every version row
already passed the scan on its write. The net effect is the same guarantee: no
unscanned version can go live. See the
site builder reference for the routes and scopes.
How it maps to what renders
manifest version (draft write --> RUO-claims scan, fail-closed)
--> publish (pointer move only, no re-scan)
--> active pointer moves to that version
--> renderer resolves store, reads active manifest
--> renderSections(template.sections, ctx) with theme tokens as CSS varsA custom frontend replaces the last two steps with store.site.get()
(GET /v1/tenants/{tenant}/site), which returns the same active manifest —
sections, nav, and theme — under the same ETag and the same honest-absence
contract (a non-live or unknown tenant is a 404, a repo failure is a 503, never
a silent empty store).
Related
- Theming — theme tokens, presets, and CSS custom properties.
- Site builder reference — draft / publish / preview / version routes.
- Build your own frontend — reading the manifest headless via the SDK.