Skip to main content

Liquid Core Contract

Liquid is a presentation layer. Shoppex TypeScript producers build the data. The platform commerce runtime owns browser behavior.

Division Of Responsibility

LayerOwns
Liquid templatesHTML, CSS hooks, Builder markers, light display conditions
Shoppex Liquid rendererLiquid engine config, filters, render context, block dispatch, package validation
Shoppex data producersproducts, stock, pricing, listings, store stats, reviews, settings enrichment
Platform commerce runtimecart, product-form behavior, coupons, checkout handoff, Alpine bindings
Shoppex edgedomain routing, cache, asset serving, and page delivery

Required Engine Behavior

Shoppex Liquid runs strict:
  • strict variables
  • strict filters
  • own-property-only object access
  • no dynamic partials
  • escaped output by default
  • separate render limits for trusted and untrusted packages
Simple example: if product.title is missing, the render should fail during validation or preview. Do not hide it with a generic fallback like “Product”.

Section Root Contract

Every section root must include:
data-builder-block="{{ block.id }}"
data-builder-block-type="{{ block.type }}"
Example:
<section
  data-builder-block="{{ block.id }}"
  data-builder-block-type="{{ block.type }}"
>
  <h1>{{ settings.title }}</h1>
</section>

Context Contract

Templates read from a producer-built StorefrontRenderContext. Good:
{% for item in homeListing.items %}
  {% render "snippets/product-card", item: item, ctx: ctx %}
{% endfor %}
Bad:
{% assign sorted = products | sort: "sold_count" %}
Product ordering belongs in the producer. The template should render the order it receives.

Safe Output

Liquid escapes output by default. Use | raw only for platform-owned HTML:
  • blocksHtml
  • themeHeadHtml
  • bootstrapScript
  • navigationHtml
  • footerHtml
  • storefrontCommerceScriptHtml
Example:
{{ blocksHtml | raw }}
Do not use raw for merchant text, product descriptions, customer data, or URLs.

Commerce Contract

Use platform hooks and Alpine factories:
<form
  data-shoppex-product-form
  x-data="productForm({{ product | json_attr | raw }})"
>
  <button type="submit">Add to cart</button>
</form>
The template owns the button markup. The platform runtime owns stock checks, quantity limits, cart payloads, coupon logic, and checkout handoff.

No Hidden Fallbacks

A real theme must never fall back to fixture templates or inferred data. Simple example: if a merchant package references sections/foo.liquid but theme-package.json does not declare it, publish should fail. Do not substitute a different template automatically.

Next Steps

Package Format

Required manifest and package files.

Cart & Checkout

Product forms, cart drawer, coupons, and checkout handoff.