> ## 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

> How layout files wrap rendered pages in an HTML shell

# 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:

| Variable                   | Holds                                                                                                                  |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `{{ 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:

```aqua theme={null}
<!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.

<Note>
  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.
</Note>

***

## 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:

```json theme={null}
// 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:

```aqua theme={null}
{# 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.

```json theme={null}
// templates/password.json
{
  "layout": "password",
  "sections": {
    "main": { "type": "password-form" }
  },
  "order": ["main"]
}
```

```aqua theme={null}
{# 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:

```aqua theme={null}
{% 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:

```json theme={null}
{
  "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.).

<Note>
  `{% 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`.
</Note>

***

## 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_mode` — `true` 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:

```aqua theme={null}
<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:

```aqua theme={null}
<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
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](/extensions/checkout-extensions) for
the supported customization model.

***

## Worked example: a full `theme.aqua`

```aqua theme={null}
<!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

<CardGroup cols={2}>
  <Card title="Theme structure" icon="folder-tree" href="/aqua/theme-structure">
    Where layout files live among the rest of the theme.
  </Card>

  <Card title="Templates and sections" icon="brackets-curly" href="/aqua/templates-and-sections">
    What `content_for_layout` actually receives.
  </Card>

  <Card title="Locales" icon="language" href="/aqua/locales">
    Translating storefront copy across locales.
  </Card>

  <Card title="Objects" icon="cube" href="/aqua/objects">
    `request`, `template`, `settings`, and other globals.
  </Card>
</CardGroup>
