Beauty Affairs HK

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:

KeySource
pageTyperequest.page_type
moneyFormat, moneyWithCurrencyFormat, shopCurrencyshop object
productImageSize, searchModeTheme settings
showPageTransition, showElementStaggering, showImageZoomingTheme settings

Later blocks in the same file, and other snippets, add more keys:

KeyAdded byFeeds
gift_productlayout/theme.liquidsrc/components/purchase-gift-manager
labelslayout/theme.liquidTranslated strings used across components
labels.medispalayout/theme.liquidRemaining-balance wording in the cart
cartRewardBannerlayout/theme.liquidsrc/components/cart-reward-banner
medispaCalendarConfigsnippets/medispa-calendar-config.liquidThe booking calendar
popupManagerConfigsnippets/popup-logic.liquidThe popup manager
needHelpPopupsections/need-help-popup.liquidThe need-help popup
packOptionssnippets/pack-options-config.liquidPack size selector
plpSegmentationTilessections/plp-segmentation-tiles.liquidCollection page tiles
selectOptionsModalConfigsections/select-options-modal.liquidQuick 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

ConcernFile
Base window.theme assignmentlayout/theme.liquid, around line 101
Labels blocklayout/theme.liquid, around line 266
Reward tier valueslayout/theme.liquid, around line 788
Type declarationssrc/@types/global.ts
Shopify object typessrc/@types/shopify.ts
Booking config typessrc/@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.

On this page