Liquid to JavaScript contract
How Liquid writes settings and metafields into the page for browser code to read, and what breaks when the two sides drift apart.
Browser code cannot read Liquid, theme settings or metafields. Liquid has to write everything the code needs into the page first.
The three ways data crosses
window.theme. layout/theme.liquid builds this global object, and individual snippets extend it. It holds
store-wide values that many scripts need, such as money formats, page type, gift product configuration and translated
labels.
data- attributes. Liquid writes section and block settings onto the element a script controls. For example, the
mini-cart drawer reads data-open-on-add and data-drawer-width from its own root element. Use this when the value
belongs to one instance of one component.
Embedded JSON script tags. Liquid writes larger structures as <script type="application/json">.
sections/product-calendar-config.liquid uses this to publish a product's selling plan groups. The booking calendar
fetches that section when it needs it, so the JSON is not on every page.
Use a data- attribute for a single value on a single component, and a JSON script tag for structured data. Add to
window.theme only for store-wide values.
What window.theme holds
layout/theme.liquid assigns the base object once, near the top of the file:
| Key | Source |
|---|---|
pageType | request.page_type |
moneyFormat, moneyWithCurrencyFormat, shopCurrency | shop object |
productImageSize, searchMode | Theme settings |
showPageTransition, showElementStaggering, showImageZooming | Theme settings |
Later blocks in the same file, and other snippets, add more keys:
| Key | Added by | Feeds |
|---|---|---|
gift_product | layout/theme.liquid | src/components/purchase-gift-manager |
labels | layout/theme.liquid | Translated strings used across components |
labels.medispa | layout/theme.liquid | Remaining-balance wording in the cart |
cartRewardBanner | layout/theme.liquid | src/components/cart-reward-banner |
medispaCalendarConfig | snippets/medispa-calendar-config.liquid | The booking calendar |
popupManagerConfig | snippets/popup-logic.liquid | The popup manager |
needHelpPopup | sections/need-help-popup.liquid | The need-help popup |
packOptions | snippets/pack-options-config.liquid | Pack size selector |
plpSegmentationTiles | sections/plp-segmentation-tiles.liquid | Collection page tiles |
selectOptionsModalConfig | sections/select-options-modal.liquid | Quick add modal |
The snippets that add these keys run in no fixed order. Each one creates its key only if it is missing, in the form
window.theme.x = window.theme.x || {}.
TypeScript does not check what Liquid produces
src/@types/global.ts declares the shape of window.theme. TypeScript checks that browser code uses that shape
consistently. Nothing checks that Liquid produces it.
A setting renamed in the schema, a snippet that stops rendering, and a template that never includes the snippet all have
the same result. The property is missing at runtime, the type still says it exists, and tsc --noEmit still passes.
When you change what Liquid writes, change src/@types/global.ts in the same commit. When a value only exists on some
pages, mark it optional in the type and handle the missing case. packOptions and plpSegmentationTiles already do
this.
Dollars and cents
Shopify's Liquid money filters expect cents. Staff enter most theme settings on this store in dollars.
A comment in snippets/medispa-calendar-config.liquid sets the rule. Values stay in dollars until the point of
formatting, where the snippet converts them to cents once, in a separate variable. Follow the same pattern. A value
converted twice is a hundred times too large, and testing often misses it.
Failure modes
The snippet never renders. A template that does not include snippets/medispa-calendar-config.liquid leaves
window.theme.medispaCalendarConfig undefined, and the calendar throws when it reads it. The Jotai atoms in
calendar-atoms.ts read that config at module scope, so the error happens on import, before any render.
A setting is renamed in the schema but not in Liquid. Liquid outputs an empty string without failing, so the value
becomes "" or 0.
A translation key is missing. {{ 'some.key' | t }} outputs translation missing: ... into a JavaScript string.
Theme Check reports these as MatchingTranslations, and this theme currently has a large number of them. See
Legacy and unused code.
A quote in a setting breaks the script. Several labels go into single-quoted JavaScript strings without | json. If
a merchant types an apostrophe into one of those settings, the syntax error stops every script in that block. Use
| json for new values instead of wrapping them in quotes by hand.
Source map
| Concern | File |
|---|---|
Base window.theme assignment | layout/theme.liquid, around line 101 |
| Labels block | layout/theme.liquid, around line 266 |
| Reward tier values | layout/theme.liquid, around line 788 |
| Type declarations | src/@types/global.ts |
| Shopify object types | src/@types/shopify.ts |
| Booking config types | src/@types/calendar.ts |
layout/theme.liquid has two siblings, layout/theme.discovery-bar.liquid and layout/theme.medispa-redesign.liquid,
which specific templates use. All three started as the same file, and about a third of their lines now differ. A change
to the globals often has to go into more than one of them. Check each layout before you call a change complete, and
check which layout a template uses before you debug a missing global.
Related
- Reference: JavaScript globals has the full key list.
- Build pipeline explains how the scripts that read these values are loaded.