Skip to main content
The page-builder catalog is the single source of truth for the templates and section types available when building hub pages. Every Membership.io surface that composes pages — backend, hub, admin, and CLI — consumes this catalog.
GET /api/v1/page-builder/catalog returns the raw catalog JSON. The response ETag header is the catalog’s content digest — send If-None-Match: "<digest>" to get a 304 Not Modified, and cache by digest.Current catalogVersion: 0.24.6 · digest: sha256:99b0727dfce6f85907d29c066d76cfb1f193fe7860be869003d10b6b777141c4

Node shape

Every node in a page tree is the same envelope. The renderer dispatches on kind — never on type — and reads the node’s content from a top-level value, a sibling of settings:
A section node is the same envelope plus a template from the table below and a children array. Putting a node’s content in settings.value instead of value, or omitting a section’s template, is accepted by the API and then silently dropped by the renderer; an unrecognized settings.weight (a CSS keyword such as "bold") is quietly discarded rather than applied. Every node also needs an id. See Node shape for the full contract, including the leaf-kind settings and the surface.background enum. The catalog also publishes higher-level registries this page does not cover — hubTemplates[] (whole-hub manifests), pageTemplates[] (page outlines), and the playlist and document seeding vocabulary. See Hub templates and the page catalog for that contract, including the versioning and pinning semantics of the artifact itself.

Templates

Node kinds

The catalog’s settingsSchema map is the authority on what each kind accepts: 33 kind:<nodeKind> entries plus the shared action, background, gradient, structural, surface, textTheme, visibility shapes, each listing typed properties with their enums, defaults, and per-field notes, split into core and presentational buckets matching the consumer profiles below. See Node shape for the shapes that most often cause a silent render drop.

Section types

Anon-safe — whether the section type is whitelisted on the backend’s sanitized pre-auth render (login / register / payment pages for logged-out visitors). It does not affect public-hub page visibility: anonymous visitors get the full published tree of any public page; only author-set access gates prune content there.

Settings vocabulary

settingsSchema is the per-kind:<k> settings contract for the page-builder node tree. Each kind’s schema splits into two tiers: core settings that every consumer — the web hub, native apps, any future renderer — must honor to render the node correctly, and presentational settings that native apps and other constrained consumers are free to ignore. Only keys and values the mio-hub renderer actually implements are listed below; this documents what’s real, not an aspirational spec. Recipes must not restate a setting’s default value — omitting a key defers to the hub’s built-in default for that key. The mio-hub renderer is the source of truth for this vocabulary, and it is declared directly in the catalog (MIO-2685).

shared:action

Canonical navigation target object (Action[‘type’]/[‘value’]/[‘params’], src/lib/page-tree/action.ts). The single shared definition referenced via ‘{type: ‘object’, shape: ‘action’}’ by kind:button.core.action, kind:content-card.core.action, and kind:image.core.action — one Action shape, three consumers (MIO-3926).

shared:background

Flattened TemplateSurface Background discriminated union (src/lib/page-tree/types.ts). Validation limitation: enum applies to ‘type’; all variant fields are optional and the validator cannot enforce which fields are required per ‘type’ value. ‘thumbnail’ is excluded per the honesty policy (decision 4) — declared as a TODO in hub types, implemented nowhere, used by no recipe.

shared:gradient

Paired with shared:background when type=‘gradient’ (src/lib/page-tree/types.ts Gradient, src/lib/theme/types.ts GradientType).

shared:textTheme

Text theme — the ONE ink a whole node is painted (the hub editor’s Text theme control: Auto / Dark / Light / Primary / Custom). Referenced as a ‘textTheme’ settings key via ‘{type: ‘object’, shape: ‘textTheme’}’ by kind:text and kind:headline (the whole node’s copy), kind:button (the label and icon ink) and kind:divider (the line’s colour); its keys nest under that field on the node (‘settings.textTheme.tone’, ‘settings.textTheme.color’). Unset — the ‘textTheme’ key absent, or an object with no ‘tone’ — is Auto: the consumer’s own default decides (text/headline: the surrounding surface’s ink, per the MIO-492 auto-contrast / ‘surface.ink’; button: derived from its fill; divider: the current colour). Each consumer’s own field description states its Auto.

shared:visibility

Per-viewport responsive show/hide (MIO-4182). Promoted into its own referenceable shape out of what used to be ‘shared:surface.visibility”s inline properties, so the SAME {mobile, desktop} vocabulary is reachable two ways with zero drift risk between them: nested under a ‘surface’ settings group (‘shared:surface.visibility’, back-compat — see that key’s own description) and as the generic top-level ‘visibility’ settings key every node kind can carry regardless of whether it has a ‘surface’ group at all (‘shared:structural.visibility’, the new MIO-4182 path). Both reference THIS ONE definition via ‘{type:‘object’, shape:‘visibility’}’ — a stored value means exactly the same thing under either path.

shared:surface

TemplateSurface (src/lib/page-tree/types.ts). Nested under a kind’s own ‘surface’ settings key (see kind:stack, kind:content-card, and the section-root template-level surface consumed via TemplateSurface). Resolved by useNodeSurface() (src/components/primitives/node-surface.tsx).

shared:structural

NOT referenced via {type:‘object’,shape:‘structural’} from a kind descriptor like the other shared shapes. This is a validator-level ADDITIVE allowlist: every node’s settings object is validated against ‘kind:<its-kind>’ UNIONED with these three keys — tab_label/slot are read generically off ANY child node’s settings by an ANCESTOR container (tabs.tsx, accordion.tsx, find-by-slot.ts), independent of the child’s own kind, and are not part of any single kind’s own *Settings TS interface; surface is read generically off ANY node’s settings by renderer.tsx’s <TemplateSurface> wrapper, gated on that node’s ‘template’ annotation naming a templates[]/pageTemplates[] entry that itself declares a ‘surface’ property (the surface-declaring template set), independent of the node’s own kind. This is an extension beyond the design spec’s literal decision-1 text (which only names shared:surface as a referenced shape); documented here because the real catalog already uses tab_label/slot on stack/text/tabs nodes and surface on section-root container nodes (see Task A1 defaults-audit notes + MIO-2685 review round 1), and excluding any of the three would make the validator reject existing recipes. MIO-2724 addendum: ‘name’/‘role’/‘salesMeta’ (REVIEW — flagged for owner confirmation, see their own descriptions) extend this additive allowlist with catalog-authoring-only narrative-arc metadata used by the page-sales recipe’s section-root nodes — distinct from any mio-hub renderer concern, and (for ‘role’) a deliberate same-name/different-vocabulary overload against kind:field.core.role, resolved safely by this validator’s kind-first-then-structural-fallback lookup order. MIO-3134/MIO-3278 addendum: ‘side’/‘logoSize’ are region-chrome settings read off the ROOT of mio-hub’s auth brand-panel tree slot (src/lib/auth/prepare-panel-region.ts) by that ONE bespoke consumer — not by NodeRenderer/useNodeSurface and not generic across every node the way tab_label/slot are. Gated the SAME way ‘surface’ is: legal only when the node’s own ‘template’ annotation names a member of the regionChrome-declaring template set (a templates[]/pageTemplates[] entry carrying a top-level ‘regionChrome’ property, e.g. ‘auth-brand-panel’) — an ungated placement would let any stray node author side/logoSize with no consumer ever reading them there. MIO-4182 addendum: ‘visibility’ extends this additive allowlist with an UNCONDITIONAL per-viewport show/hide key (shared:visibility shape) — unconditional like tab_label/slot, NOT gated like surface/side/logoSize, so it validates on every node kind regardless of ‘template’. It is a NEW top-level path onto the exact same {mobile, desktop} vocabulary ‘shared:surface.visibility’ has always carried; see both keys’ own descriptions for the back-compat/precedence story. MIO-4331 addendum: ‘visibleFrom’ extends this additive allowlist with an UNCONDITIONAL scope-presence gate (see its own description) — like ‘visibility’ it is read by the hub renderer for every kind. Status honesty (MIO-2584): this vocabulary addition documents only what the mio-hub renderer implements in the SAME change set it ships with — see the PR description for the exact landing commit.

kind:stack

kind:row

kind:grid

kind:carousel

kind:horizontal-scroll

kind:tabs

No settings. The Tabs component reads NO settings of its own (PRIMITIVE_DEFAULTS explicitly documents this: “tabs — the component reads no settings at all”). Its per-child tab label comes from shared:structural’s tab_label on each CHILD’s own settings, not from a tabs-kind setting.

kind:container

No PRIMITIVE_DEFAULTS entry (container: {}) — the DS-intended maxWidth:‘content’ default has NO inline ’??’ fallback in container.tsx, deliberately undeclared per that file’s own comment (would change rendering the moment MIO-2358 wires cascade layer 1).

kind:content-card

kind:accordion

kind:video

kind:divider

kind:progress-ring

kind:headline

kind:text

kind:image

EXCLUDES ‘width’/‘height’ from ImageSettings — the hub interface comment states they are ‘presently no-ops’ (no longer forwarded to the rendered <img>, ui/thumbnail owns that element with no sizing passthrough), kept on the TS contract only for BE-served-data forward-compat parsing, not for authoring. Per the honesty policy, a key with no rendering effect does not enter the vocabulary. Same reasoning removed ‘outline’ (owner decision 2026-09-30, retired image hairline): image.tsx now hardcodes ‘outline={false}’ on ui/thumbnail regardless of the setting; the TS ‘ImageSettings.outline?’ field stays on the hub contract only as a retired, documented-ignored field for old saved trees, but the key itself no longer exists in this vocabulary. ‘kind:media-slot”s own ‘outline’ is unaffected — that renderer still applies it.

kind:button

kind:icon

kind:field

kind:media-slot

kind:cta-slot

kind:file-player

kind:file-attachments

kind:search-bar

kind:quote

kind:banner

kind:countdown

kind:theme-toggle

No settings.

kind:plan-group

kind:plan-card

kind:plan-price

kind:doodle

kind:featured-icon

Consumer profiles

  • ios-min — templates: hero; settings: core
  • web — templates: all templates; settings: core+presentational