Data shape
The fundamental design principle of tt-blocks: the editor → D1 → metafield → __TTB_BLOCKS__ → storefront rendering five layers all share the same config object shape, guarded by 22 audit scripts.
5-layer data flow
Section titled “5-layer data flow”createDefault(type, locale) ← initial component defaults ├─ preset:* → engine + @widget/presets (wizard preset path) └─ single → COMPONENT_DEFS[type].createDefault ↓ both paths land on the same canonical defaultseditor reducer state ← app/lib/widget-state.jsx ↓ merchant edits fields in the editorD1.widgets.config_json + metafield ← saveWidget ↓ Shopify pushes metafield to storefront themewindow.__TTB_BLOCKS__[id] ← injected by widget.liquid ↓ widget.js reads config and rendersWidgetRenderer (Preact tree) ← packages/widget-uiEvery layer reads the same shape — what the merchant sees in the admin editor preview is what renders on the storefront.
Top-level config fields
Section titled “Top-level config fields”Stored in D1 widgets.config_json and mirrored to Shopify app.metafields.widget[<key>]:
{ "key": "ttb-xxxxxxxx", "content": [ /* top-level component tree */ ], "container": { /* wrapper styling */ }, "enabled": true, "userCss": "", "version": "1", "breakpoint": "768px", "hideElements": [], "customAnchorSelectors": { "product": [], "productList": [], "productMedia": [], "exclude": [] }, "locales": {}, "placement": { /* see reference/placement-modes */ }, "visibility": { /* see editor/display 9 dimensions */ }}| Field | Purpose |
|---|---|
key | Widget identifier, matches D1 widget_key column |
content | Top-level component array — see Widget structure |
container | Widget wrapper background / border / radius / padding / margin / displayAnimation |
enabled | Widget master switch |
userCss | Merchant-supplied CSS (Pro only) |
version | Schema version, currently "1" |
breakpoint | Mobile breakpoint, default "768px" |
hideElements | CSS selectors to hide theme elements (Pro only) |
customAnchorSelectors | Merchant-supplied theme DOM selectors (escape hatch, max 24) |
locales | i18n translation map: { locale: { "componentId.path": "translated text" } } |
placement | Mount location — see Placement modes |
visibility | Display rules — see Display tab |
GSC-compatible fields vs project extensions
Section titled “GSC-compatible fields vs project extensions”The tt-blocks data shape largely aligns with GSC Blinks (the open-source reference implementation), enabling fixture interop and validation.
| Field | Source | Notes |
|---|---|---|
key / content / container / enabled / version / breakpoint / hideElements / locales | GSC-compatible | Same shape as GSC fixtures |
userCss | GSC-compatible | Same field name as GSC |
placement | Project extension | No GSC equivalent — describes where widget mounts |
visibility | Project extension | No GSC equivalent — describes who sees the widget |
customAnchorSelectors | Project extension | Merchant override for the default 65-theme selector matrix |
Evolution contract: strict additive-only
Section titled “Evolution contract: strict additive-only”All persistence layers (D1 / metafield / __TTB_BLOCKS__) are strictly add-only:
- ✅ Add optional fields (with default fallbacks)
- ❌ Remove existing fields
- ❌ Change field types
- ❌ Promote optional → required
Reason: existing merchant widgets must not break when the schema evolves; the renderer must read all historical versions.
Enforced by widget-schema-evolution-audit — any non-additive PR fails CI. See the 7 forward-compat contracts in Multi-axis Token Bundle.
Related concepts
Section titled “Related concepts”- The data shape is the physical embodiment of Widget structure (the
contentfield carries the component tree) - The “color scheme” layer is described in Multi-axis Token Bundle
- Free / Pro tier classification of visibility fields is in Billing tier
- The
placementfield determines Widget types