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

# Templates and Sections

> How JSON templates compose sections and blocks into pages

# Templates and Sections

A LaunchMyStore page is a **template** that composes **sections**, each of
which composes **blocks**. Templates declare composition in JSON, sections
provide reusable rendering units with their own schema, and blocks layer
per-instance configuration on top.

This page explains:

1. How JSON templates declare page composition.
2. How template resolution picks the right file for a URL.
3. How sections are reused across templates.
4. How block-level configuration is layered on top.
5. How **section groups** turn the header / footer into editable units.
6. How the legacy `.liquid` / `.aqua` template fallback works.

***

## Anatomy of a JSON template

A JSON template is a manifest. It lists the sections to render and their
display order. The renderer reads it once, looks up each section's Aqua
source from `sections/<type>.aqua`, merges the merchant's settings, and
emits HTML.

```json theme={null}
{
  "sections": {
    "main": {
      "type": "product-information",
      "settings": {
        "show_vendor": true,
        "show_quantity_selector": true
      },
      "blocks": {
        "title": {
          "type": "title",
          "settings": { "size": "large" }
        },
        "price": { "type": "price" },
        "buy_buttons": {
          "type": "buy-buttons",
          "settings": { "show_dynamic_checkout": true }
        }
      },
      "block_order": ["title", "price", "buy_buttons"]
    },
    "recommendations": {
      "type": "product-recommendations",
      "settings": { "products_to_show": 4 }
    }
  },
  "order": ["main", "recommendations"]
}
```

### Required keys

| Key        | Type            | Required | Description                                             |
| ---------- | --------------- | -------- | ------------------------------------------------------- |
| `sections` | object          | Yes      | Map of `section_id → section instance`.                 |
| `order`    | array           | Yes      | Render order. Items must be keys in `sections`.         |
| `layout`   | string \| false | No       | Override the layout (`"layout":"password"` or `false`). |
| `wrapper`  | string          | No       | Optional wrapper element override.                      |

### Section instance

| Key           | Type   | Description                                                        |
| ------------- | ------ | ------------------------------------------------------------------ |
| `type`        | string | Filename of the section template under `sections/` (no extension). |
| `settings`    | object | Merchant values for the section's schema settings.                 |
| `blocks`      | object | Map of `block_id → block instance`.                                |
| `block_order` | array  | Block render order.                                                |
| `disabled`    | bool   | If true, the section is skipped at render.                         |
| `custom_css`  | string | Optional per-instance CSS injected with a scoped data attribute.   |

### Block instance

| Key        | Type   | Description                                                      |
| ---------- | ------ | ---------------------------------------------------------------- |
| `type`     | string | Block type as declared in the section's schema `blocks` array.   |
| `settings` | object | Merchant values for the block's settings.                        |
| `static`   | bool   | Marks the block as fixed — merchants can't remove or reorder it. |
| `name`     | string | Display name in the editor (supports `t:` translations).         |
| `blocks`   | object | Nested blocks (for blocks that themselves render blocks).        |

***

## Template resolution

When a request hits a URL, the renderer picks a template file by the
following rules, in order. The first one that exists wins.

For `/products/<handle>` where the product has `template_suffix:
"alternate"`:

1. `templates/product.alternate.json`
2. `templates/product.alternate.aqua`
3. `templates/product.alternate.liquid`
4. `templates/product.json`
5. `templates/product.aqua`
6. `templates/product.liquid`
7. — fail with 500 —

For URL patterns where the **handle** itself is a template suffix (`page`
and `customers/*`), the lookup is even simpler:

```
/pages/contact
  → templates/page.contact.json
  → templates/page.contact.aqua
  → templates/page.json   (fallback)
```

Template resolution is fully cached — after the first request there is
no per-request lookup cost.

### Selecting a template suffix

Merchants can attach a per-resource template suffix through:

* The product / page / collection / blog / article admin form
  (`template_suffix` column on the resource).
* The URL query parameter `?view=<suffix>`, primarily used for AJAX
  section fetches.

### Layout override per template

A JSON template can override the layout that wraps it:

```json theme={null}
{
  "layout": "password",
  "sections": { ... },
  "order": [ ... ]
}
```

`"layout": false` disables the layout entirely — the rendered body becomes
the response. See [Layouts](/aqua/layouts) for details.

***

## How a section renders

Take this snippet from a JSON template:

```json theme={null}
"main": {
  "type": "featured-collection",
  "settings": { "title": "New arrivals", "products_count": 6 }
}
```

The renderer:

1. Opens `sections/featured-collection.aqua`.
2. Reads its `{% schema %}` block (parsed once and cached).
3. Builds a `section` object exposing `section.id = "main"`,
   `section.settings = { ... }`, `section.blocks = [...]`.
4. Strips the schema block from the source.
5. Compiles and renders the remaining template with `section` in scope.

Inside the section template:

```aqua theme={null}
{# sections/featured-collection.aqua #}
<section
  class="featured-collection color-{{ section.settings.color_scheme }}"
  {{ section.lms_attributes }}
>
  <h2>{{ section.settings.title }}</h2>

  {% if section.settings.collection != blank %}
    <div class="grid grid-cols-{{ section.settings.columns }}">
      {% for product in section.settings.collection.products
                       limit: section.settings.products_count %}
        {% render 'product-card', product: product %}
      {% endfor %}
    </div>
  {% endif %}
</section>

{% schema %}
{
  "name": "Featured collection",
  "tag": "section",
  "class": "featured-collection",
  "settings": [
    { "type": "text",       "id": "title",          "label": "Title",       "default": "Featured collection" },
    { "type": "collection", "id": "collection",     "label": "Collection" },
    { "type": "range",      "id": "products_count", "label": "Count", "min": 2, "max": 12, "step": 1, "default": 4 },
    {
      "type": "select", "id": "columns", "label": "Columns",
      "options": [
        { "value": "2", "label": "2" },
        { "value": "3", "label": "3" },
        { "value": "4", "label": "4" }
      ],
      "default": "4"
    }
  ],
  "presets": [
    { "name": "Featured collection", "settings": { "products_count": 4 } }
  ]
}
{% endschema %}
```

`section.lms_attributes` emits
`data-section-id="main" data-section-type="featured-collection"` for the
editor.

***

## Blocks inside sections

Blocks layer over sections. A section's schema declares which block types
it accepts; the JSON template instance picks values per block and lists them
in `block_order`.

### Section schema declaration

```json theme={null}
{
  "name": "Slideshow",
  "blocks": [
    {
      "type": "slide",
      "name": "Slide",
      "limit": 5,
      "settings": [
        { "type": "image_picker", "id": "image",   "label": "Image" },
        { "type": "text",         "id": "heading", "label": "Heading" }
      ]
    },
    {
      "type": "video_slide",
      "name": "Video Slide",
      "settings": [
        { "type": "video_url", "id": "video", "label": "Video URL" }
      ]
    },
    { "type": "@app" }
  ],
  "max_blocks": 8
}
```

`{ "type": "@app" }` reserves a slot for any storefront block installed by
an app extension.

### Rendering blocks

```aqua theme={null}
{% for block in section.blocks %}
  <div {{ block.lms_attributes }} class="slide slide--{{ block.type }}">
    {% case block.type %}
      {% when 'slide' %}
        <img src="{{ block.settings.image | image_url: width: 1200 }}" alt="">
        <h2>{{ block.settings.heading }}</h2>
      {% when 'video_slide' %}
        {{ block.settings.video | external_video_tag }}
      {% when '@app' %}
        {% content_for 'block', id: block.id, type: block.type %}
    {% endcase %}
  </div>
{% endfor %}
```

`section.blocks` is the ordered iterator (matches `block_order`). Looking up
by id (`section.blocks['title']`) is also supported.

### Static blocks

A block instance with `"static": true` is locked — merchants can't move,
duplicate, or delete it in the editor, and it always renders at its declared
position. Use this for blocks that are structurally required (the product
title on a product page, for example):

```json theme={null}
"blocks": {
  "media-gallery": {
    "type": "_product-media-gallery",
    "static": true,
    "settings": { "media_columns": "two" }
  }
}
```

Private block types (prefixed with `_`) combined with `"static": true` is
the canonical way to ship "fixed" elements that the merchant can still
*configure* but can't *remove*.

***

## Section reuse across templates

Sections are theme-wide. The same `featured-collection.aqua` file can appear
in `index.json`, `collection.json`, and `page.welcome.json`, each with
different settings.

```json theme={null}
// templates/index.json
{
  "sections": {
    "hero": { "type": "image-banner", "settings": { "title": "Summer" } },
    "featured": {
      "type": "featured-collection",
      "settings": { "title": "New arrivals", "products_count": 8 }
    }
  },
  "order": ["hero", "featured"]
}
```

```json theme={null}
// templates/page.welcome.json
{
  "sections": {
    "welcome_text": { "type": "rich-text", "settings": { ... } },
    "featured": {
      "type": "featured-collection",
      "settings": { "title": "Bestsellers", "products_count": 4 }
    }
  },
  "order": ["welcome_text", "featured"]
}
```

The renderer doesn't deduplicate — each instance gets its own compiled
context and its own DOM subtree.

### Restricting section availability

A section's schema can opt out of certain templates via `enabled_on` /
`disabled_on`:

```json theme={null}
{
  "enabled_on":  { "templates": ["index", "collection"], "groups": ["header", "footer"] },
  "disabled_on": { "templates": ["password", "cart"] }
}
```

The editor uses these to filter the "Add section" picker. The renderer will
still execute a section that ended up on a disallowed template — these
restrictions are advisory, not enforced.

***

## Section groups

A **section group** is an ordered list of sections that travel together,
declared as a JSON manifest under `sections/`. Most themes have at least
`header-group.json` and `footer-group.json`.

```json theme={null}
// sections/header-group.json
{
  "type": "header",
  "name": "Header",
  "sections": {
    "announcement-bar": {
      "type": "announcement-bar",
      "settings": { "message": "Free shipping over $50" }
    },
    "header_main": {
      "type": "header",
      "settings": { "show_search": true, "logo_width": 120 }
    }
  },
  "order": ["announcement-bar", "header_main"]
}
```

The shape is identical to a JSON template, with an added `type` (the group
name) and `name` (label shown in the editor).

### The `{% sections '<group>' %}` tag

To render a group, use the `sections` tag from any Liquid/Aqua context —
usually the layout:

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

The tag loads `sections/<name>-group.json`, then iterates the `order` array
and renders each section in turn, just like a JSON template.

<Note>
  `{% sections '...' %}` (plural) renders a **group**.
  `{% section '...' %}` (singular, legacy) renders a single static section
  file. Most themes prefer the group form because it gives merchants a
  single-button entry to reorder header/footer elements in the editor.
</Note>

### Group types beyond header / footer

You can ship any group your theme needs:

* `sections/overlay-group.json` — global modals, cart drawers, search
  popups.
* `sections/aside-group.json` — sidebar widgets for collection pages.
* `sections/popups-group.json` — newsletter / age gate / cookie banner.

Reference them with `{% sections 'overlay-group' %}` in the layout. The
editor surfaces each group as a separately-editable region.

***

## Liquid template fallback

For pages that don't fit the JSON model, a theme can ship a plain
`.aqua` / `.liquid` template that is the whole page body:

```aqua theme={null}
{# templates/gift_card.aqua #}
{% layout 'gift_card' %}

<div class="gift-card">
  <h1>{{ gift_card.id }}</h1>
  <p>Balance: {{ gift_card.balance | money }}</p>
  <img
    src="{{ gift_card.qr_identifier | qr_code }}"
    alt="QR code for redeeming this gift card"
  >
</div>
```

The renderer:

1. Detects `{% layout '<name>' %}` if present and switches the wrapper
   layout (`layout/<name>.aqua`).
2. Detects `{% layout none %}` and renders the file body **without** any
   layout — useful for AJAX endpoints.
3. Compiles the file once and renders with the page's global object in
   scope (e.g. `gift_card` for `templates/gift_card.aqua`).

You can mix forms within one theme: keep `index.json` as a section
composition but render `gift_card.aqua` as a single-file template.

***

## Worked example: `product.json`

A minimal product page:

```json theme={null}
{
  "sections": {
    "main": {
      "type": "product-information",
      "blocks": {
        "media":   { "type": "_product-media-gallery", "static": true },
        "title":   { "type": "title" },
        "price":   { "type": "price" },
        "variant": { "type": "variant-picker" },
        "buy":     {
          "type": "buy-buttons",
          "settings": { "show_dynamic_checkout": true }
        },
        "description": {
          "type": "description",
          "settings": { "show_full": true }
        }
      },
      "block_order": ["media", "title", "price", "variant", "buy", "description"]
    },
    "related": {
      "type": "product-recommendations",
      "settings": { "products_to_show": 4, "intent": "related" }
    },
    "recently_viewed": {
      "type": "product-recommendations",
      "settings": { "products_to_show": 4, "intent": "recently_viewed" }
    }
  },
  "order": ["main", "related", "recently_viewed"]
}
```

Rendered against the URL `/products/blue-shirt`:

1. The renderer resolves `templates/product.json`.
2. Fetches the product (handle `blue-shirt`) and exposes it as `product`.
3. Renders sections in `order`: `main` → `related` → `recently_viewed`.
4. `main` is `product-information.aqua` with the schema-driven blocks
   above; the section's loop renders title → price → variant → buy →
   description.
5. The result is concatenated as `content_for_layout` and wrapped in
   `layout/theme.aqua`.

The same `product-information` section file is reused on
`templates/product.alternate.json` with different settings and a different
block order — no duplication of the section's HTML or schema.

***

## Patterns

### Per-handle landing pages

For `/pages/about`, ship `templates/page.about.json` with custom sections.
The default `page.json` still handles every other page.

### Themed collection pages

Add `templates/collection.featured.json` with a different hero and
filtering UI, then let merchants set `template_suffix = "featured"` on
selected collections.

### Section sharing between template types

Define `sections/newsletter.aqua` once and reference it from `index.json`,
`page.json`, `cart.json`, and `footer-group.json`. Merchants edit each
instance independently.

### AJAX section fetches

`/products/<handle>?section_id=related` returns just that section's HTML.
Middleware intercepts and rewrites to `/api/themes/render-section`. Use
this for "Load more" / "Quick view" / cart drawer updates without a full
page navigation.

***

## Next steps

<CardGroup cols={2}>
  <Card title="Schema" icon="code" href="/aqua/schema">
    Define a section's settings, blocks, and presets.
  </Card>

  <Card title="Input settings" icon="sliders" href="/aqua/input-settings">
    Every available input type for schemas.
  </Card>

  <Card title="Layouts" icon="frame" href="/aqua/layouts">
    Wrap the rendered body in HTML.
  </Card>

  <Card title="Tags" icon="code" href="/aqua/tags">
    `{% section %}`, `{% sections %}`, `{% render %}`, and friends.
  </Card>
</CardGroup>
