Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.launchmystore.io/llms.txt

Use this file to discover all available pages before exploring further.

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:
<!doctype html>
<html lang="{{ request.locale.iso_code }}">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    {%- if settings.favicon != blank -%}
      <link
        rel="icon"
        type="image/png"
        href="{{ settings.favicon | image_url: width: 32, height: 32 }}"
      >
    {%- endif -%}

    <title>{{ page_title }}</title>
    {% if page_description %}
      <meta name="description" content="{{ page_description | escape }}">
    {% endif %}

    <link rel="stylesheet" href="{{ 'application.css' | asset_url }}">

    {{ content_for_header }}
  </head>

  <body class="template-{{ template }}">
    <div id="header-group">
      {% sections 'header-group' %}
    </div>

    <main id="MainContent" role="main">
      {{ content_for_layout }}
    </main>

    <div id="footer-group">
      {% sections 'footer-group' %}
    </div>

    <script src="{{ 'theme.js' | asset_url }}" defer></script>
  </body>
</html>

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/:
layout/
├── theme.aqua          ← default, used for every page unless overridden
├── password.aqua       ← used when the store is in "coming soon" mode
└── minimal.aqua        ← used by templates that opt in

Selecting an alternate layout from a JSON template

A JSON template can name a layout file:
// templates/page.legal.json
{
  "layout": "minimal",
  "sections": {
    "main": { "type": "page", "settings": { "show_meta": false } }
  },
  "order": ["main"]
}
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:
{# templates/gift_card.aqua #}
{% layout 'gift_card' %}

<div class="gift-card">
  <h1>{{ gift_card.id }}</h1>
  <p>{{ gift_card.balance | money }}</p>
</div>
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.
// templates/password.json
{
  "layout": "password",
  "sections": {
    "main": { "type": "password-form" }
  },
  "order": ["main"]
}
{# layout/password.aqua — example minimum #}
<!doctype html>
<html lang="{{ request.locale.iso_code }}">
  <head>
    <meta charset="utf-8">
    <title>{{ shop.name }} — Opening soon</title>
    <link rel="stylesheet" href="{{ 'password.css' | asset_url }}">
    {{ content_for_header }}
  </head>
  <body class="template-password">
    {{ content_for_layout }}
  </body>
</html>

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:
{% layout none %}

<div class="card">
  <h2>{{ product.title }}</h2>
  <p>{{ product.price | money }}</p>
</div>
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:
{
  "layout": false,
  "sections": {
    "card": { "type": "product-card-fragment" }
  },
  "order": ["card"]
}
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:
<body class="template-{{ template }} {% if customer %}is-logged-in{% endif %}">
settings (theme settings from config/settings_data.json) is also available globally — most layouts emit them as CSS custom properties:
<style>
  :root {
    --color-primary:   {{ settings.color_primary }};
    --color-secondary: {{ settings.color_secondary }};
    --page-width:      {{ settings.page_width }}px;
  }
</style>

A note on checkout.liquid

LaunchMyStore’s checkout is not rendered through Liquid. It’s a native React 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 (common in older Shopify themes), 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

<!doctype html>
<html
  class="no-js{% if request.design_mode %} lms-design-mode{% endif %}"
  lang="{{ request.locale.iso_code }}"
>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">

    {%- if settings.favicon != blank -%}
      <link
        rel="icon"
        type="image/png"
        href="{{ settings.favicon | image_url: width: 32, height: 32 }}"
      >
    {%- endif -%}

    {%- render 'meta-tags' -%}
    {%- render 'stylesheets' -%}
    {%- render 'fonts' -%}
    {%- render 'theme-styles-variables' -%}
    {%- render 'color-schemes' -%}

    {% if request.design_mode %}
      {%- render 'theme-editor' -%}
    {% endif %}

    {{ content_for_header }}
  </head>

  <body class="page-width-{{ settings.page_width }} template-{{ template }}">
    {% render 'skip-to-content-link', href: '#MainContent', text: 'accessibility.skip_to_text' %}

    <div id="header-group">
      {% sections 'header-group' %}
    </div>

    <main
      id="MainContent"
      class="content-for-layout"
      role="main"
      data-template="{{ template }}"
    >
      {{ content_for_layout }}
    </main>

    <div id="footer-group">
      {% sections 'footer-group' %}
    </div>

    <script src="{{ 'theme.js' | asset_url }}" type="module" defer></script>
  </body>
</html>
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.