Data model
The TemplateData contract — component nodes, propsSchema, slots vs children, and field ownership.
TemplateData is the JSON document a store renders. It is the single contract shared by the
Lithium renderer (TypeScript), the merchant editor (Flutter), the AI generators, and the
template kit compiler.
Root shape
{
props?: { // template-level prefs
theme?: { mode: 'light' | 'dark' | 'system', ... },
corners?: { ... },
...
},
i18n?: { // theme translations (from kit locales/*.json)
defaultLocale: string,
locales: string[],
messages: Record<string, Record<string, any>>,
},
pages: {
[pageId: string]: {
props: Record<string, any>,
sections: {
[sectionId: string]: { components: TemplateDataComponent[] }
}
}
}
}Component node
{
type: string, // registry key | "custom" | "reference" | "grid" | "flex" | "container"
instanceId?: string | null, // stable identity (editor selection + React key)
title?: string | null, // editor label only
props: Record<string, any>, // merchant-editable values
// Layout types only
children?: TemplateDataComponent[] | null,
// Custom / slotted components
code?: string | null, // JSX source for type:"custom"
propsSchema?: Record<string, PropDef> | null,
slotsSchema?: Record<string, { name?: string | null, maxChildren?: number | null }> | null,
slots?: Record<string, TemplateDataComponent[]> | null,
slotsLayout?: Record<string, SlotLayoutNode> | null, // editor chrome only
// Library placement
refId?: string,
refVersion?: number,
}Field ownership
This split is what keeps developer code and merchant edits from clobbering each other:
| Field | Who edits it | Purpose |
|---|---|---|
props | Merchant (inspector) | Values matching propsSchema |
code | Developer / AI | The component's identity — not a prop |
propsSchema | Developer / AI | Drives the inspector UI |
slotsSchema | Developer / AI | Declares named slots |
slots | Merchant (editor) | What's inside each slot |
slotsLayout | Developer / AI | Editor-only responsive arrangement of slots |
children | Editor | Layout types only |
code, propsSchema, slotsSchema, slots and slotsLayout are top-level fields.
Never nest them inside props — both the renderer and the editor break.
PropDef — what the inspector renders
Each propsSchema entry describes one editable prop:
{
type: 'string' | 'number' | 'boolean' | 'text' | 'array' | 'object',
tool?: { type: 'slider', min, max, step }
| { type: 'select', options: string[] }
| { type: 'color' } | { type: 'image' } | { type: 'code' } | { type: 'text' },
default?: any,
items?: PropDef, // for arrays
propsSchema?: Record<string, PropDef>, // for objects
name?: string, // display label
}Example — a hero with an editable heading, image and CTA list:
{
"heading": { "type": "string", "name": "Heading" },
"background": { "type": "string", "tool": { "type": "image" }, "name": "Background" },
"ctas": {
"type": "array",
"name": "Buttons",
"items": {
"type": "object",
"propsSchema": {
"label": { "type": "string" },
"href": { "type": "string" }
}
}
}
}Layout children vs slots
| Mechanism | Used by | Meaning |
|---|---|---|
children[] | grid, flex, container | Plain nested components inside a layout |
slots{ id: [] } | custom with slotsSchema | Named insertion points the merchant can fill; delivered to your JSX pre-rendered as props.slots[slotId] |
Single-main pages (product, checkout, thank_you) use one custom shell component whose
layout lives entirely in slots — the merchant rearranges the page without breaking your code.
Reference components
A reference node places a component from the shared library by id instead of inlining code:
{ "type": "reference", "refId": "cmp_...", "refVersion": 3, "props": {}, "slots": {} }References are resolved server-side into full custom nodes before rendering (missing refs
are skipped fail-soft). This is how one component source powers many placements — fix it once,
every placement updates. Manage the library via the templateComponents
SDK repository.
Identity rules
- Give every node a unique, stable
instanceId(e.g.custom_dawn_header_home). - Never regenerate ids when editing code or props — editor selection and React reconciliation depend on them.
Schema (catalog)
The TemplateSchema describes what the editor can offer:
{
name, version, schema,
components: { [type]: { name, category?, propsSchema?, slots? } },
pages: { [pageId]: { name, path, propsSchema, sections: {...} } },
propsSchema?: { ... } // global theme props
}Templates ship their own schema overlay (schema.ts in the kit) merged over Lithium's
built-ins; the merged result is published as StoreTemplate.schema.