Skip to main content

Layouts

A layout is the outermost HTML wrapper for a rendered page — the <!doctype html>, <html>, <head>, and <body> tags, plus any global navigation, scripts, and styles that appear on every page. Every theme ships at least one layout file (layout/theme.aqua), and that file is used for every page render unless the page explicitly chooses otherwise.

The default layout

layout/theme.aqua is the canonical entry point for every rendered page. It receives two special variables from the renderer:
VariableHolds
{{ content_for_header }}Platform-injected <head> content: analytics scripts, meta tags, OG tags, <link> preloads, customizer-mode scripts.
{{ content_for_layout }}The rendered page body — the result of running the resolved template through Liquid.
A minimal but complete layout:

Requirements

A layout file must:
  1. Be a complete HTML document — open with <!doctype html> and emit <html>, <head>, and <body>.
  2. Include {{ content_for_header }} somewhere inside <head>. Without it, analytics scripts, the theme-editor bridge, view-transition markers, and OG tags will not be injected.
  3. Include {{ content_for_layout }} somewhere inside <body>. Without it, the rendered template body is dropped silently and the page looks empty.
A layout should:
  • Reference theme settings via {{ settings.* }} for global CSS variables, fonts, and feature flags.
  • Render {% sections 'header-group' %} and {% sections 'footer-group' %} if the theme uses section groups for navigation (most modern themes do).
  • Set lang="{{ request.locale.iso_code }}" on <html> so screen readers pick the right voice.
A layout must not:
  • Use {% section '<name>' %} or {{ content_for_layout }} more than once per render. Only the first occurrence is populated; later references return empty.
  • Include a {% schema %} block. Schemas live on sections and blocks, not layouts.

What content_for_header injects

The platform reserves content_for_header for HTML that must be present in <head> for the storefront to work correctly. At minimum, it includes:
  • Analytics and tracking scripts that the merchant has enabled (GA4, GTM, Meta Pixel, TikTok Pixel, Pinterest, Snapchat, plus any installed app scripts).
  • The theme editor’s communication bridge (only when request.design_mode is true).
  • View-transition markers, preconnect hints, and resource preloads computed from the current page’s data.
  • Open Graph and Twitter Card metadata for the current page.
You don’t need to handle any of this manually — just emit {{ content_for_header }} and the renderer fills it in.
If you also need to inject your own <head> content per page, do that through the section schema (the editor exposes a “Theme settings → Advanced → Custom code” pattern in many themes) rather than hardcoding into the layout. That way merchants can edit without touching template files.

Multiple layouts

A theme can ship more than one layout. Each is a separate file under layout/:

Selecting an alternate layout from a JSON template

A JSON template can name a layout file:
The renderer reads "layout": "minimal", then loads layout/minimal.aqua. The page body is rendered into {{ content_for_layout }} of that file instead of the default. If the file doesn’t exist, the renderer falls back to layout/theme.aqua with a warning in the logs.

Selecting an alternate layout from a Liquid template

A .aqua / .liquid template can declare its layout with the {% layout %} tag at the top of the file:
This is exactly equivalent to the JSON "layout": "gift_card" form. Both resolve to layout/gift_card.aqua.

The password.aqua layout

Stores in “coming soon” mode return templates/password.json. By convention this template sets "layout": "password", which renders into layout/password.aqua — a stripped-down shell with just the password form and no global navigation.

Bypassing the layout

For AJAX section reloads, embedded widgets, and email rendering, you often want the rendered body without any HTML wrapper. There are two ways to do this.

{% layout none %} in a Liquid template

In a .aqua / .liquid template, declare:
The renderer treats the file body as the entire response — no <!doctype>, no <html>, no header / footer groups. The single <div> is returned as-is.

"layout": false in a JSON template

A JSON template can opt out of the layout the same way:
The renderer runs the sections in order, concatenates their HTML, and returns it directly.

URL-level bypass

Section AJAX fetches use a different mechanism — ?section_id=<id> is intercepted by middleware and routed to /api/themes/render-section, which renders one section without ever loading a layout. You typically don’t write ?section_id= URLs yourself; the theme’s JavaScript builds them (cart drawer updates, infinite scroll, predictive search, etc.).
{% layout none %} and "layout": false skip {{ content_for_header }} entirely. If you’re rendering an embeddable widget that still needs analytics, choose a minimal layout file rather than none.

Layout selection precedence

When the renderer chooses which layout to use, it consults these sources in order:
  1. {% layout '<name>' %} or {% layout none %} inside a Liquid template. Wins over everything else.
  2. "layout": "<name>" or "layout": false in a JSON template.
  3. ?layout=<name> query parameter on the request URL. Reserved for internal tooling (customizer preview, render-section endpoints) and not generally used by storefront URLs.
  4. layout/theme.aqua — the default.
The resolved name is suffixed with .aqua (or .liquid if .aqua is absent) and looked up under layout/.

The request and template globals

Layouts have access to the full global object catalogue. Two are particularly useful in a layout:
  • request — the current request. Most useful fields:
    • request.design_modetrue when rendering inside the theme editor’s preview iframe. Use this to load the editor bridge or extra debug helpers.
    • request.locale.iso_code — the active locale’s two-letter code (e.g. "en", "fr"). Set this on <html lang=...>.
    • request.path — the request path.
  • template — the active template name ("product", "collection.featured", etc.). Useful for body classes:
settings (theme settings from config/settings_data.json) is also available globally — most layouts emit them as CSS custom properties:

A note on checkout.liquid

LaunchMyStore’s checkout is not rendered through Liquid. It’s a native surface built into the platform, with extensibility provided through checkout extensions and post-purchase extensions rather than a Liquid template. If your theme ships a layout/checkout.liquid file (checkout layouts in some Liquid platforms), it is ignored. Checkout customization happens through:
  • Checkout extensions — UI extensions injected at named extension points.
  • Post-purchase extensions — pages shown after order placement.
  • Branding — colors, fonts, and logo are read from the merchant’s brand settings, not from checkout.liquid.
See the Extensions documentation for the supported customization model.

Worked example: a full theme.aqua

Every piece of this is editable by the theme author. The two {{ content_for_* }} calls are the contract — everything else is yours.

Next steps

Theme structure

Where layout files live among the rest of the theme.

Templates and sections

What content_for_layout actually receives.

Locales

Translating storefront copy across locales.

Objects

request, template, settings, and other globals.