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.

Theme Structure

Every LaunchMyStore theme is a folder of plain text files laid out in a fixed, Shopify-compatible directory structure. When you upload a theme ZIP, the installer copies its contents into public/themes/<themeId>/ on the storefront, builds a schema index, then serves it to merchants of that store. This page is the canonical reference for that directory tree.
LaunchMyStore is 100% Liquid-compatible. Existing Shopify themes (Dawn, Impulse, Symmetry, etc.) drop in unchanged. New themes may use the .aqua extension for template files, but the two are interchangeable — themes can mix .liquid and .aqua files freely.

The full tree

A typical theme directory looks like this:
public/themes/<themeId>/
├── assets/
│   ├── application.css
│   ├── application.js
│   ├── theme.js
│   ├── critical.js
│   ├── logo.svg
│   └── ...
├── blocks/
│   ├── text.aqua
│   ├── button.aqua
│   ├── image.aqua
│   └── ...
├── sections/
│   ├── header.aqua
│   ├── header-group.json
│   ├── footer.aqua
│   ├── footer-group.json
│   ├── featured-product.aqua
│   ├── featured-collection.aqua
│   └── ...
├── snippets/
│   ├── meta-tags.aqua
│   ├── price.aqua
│   ├── product-card.aqua
│   ├── icon-search.aqua
│   └── ...
├── templates/
│   ├── index.json
│   ├── product.json
│   ├── product.alternate.json
│   ├── collection.json
│   ├── cart.json
│   ├── page.json
│   ├── page.contact.json
│   ├── blog.json
│   ├── article.json
│   ├── search.json
│   ├── 404.json
│   ├── password.json
│   ├── list-collections.json
│   └── gift_card.aqua
├── layout/
│   ├── theme.aqua
│   └── password.aqua
├── config/
│   ├── settings_data.json
│   ├── settings_schema.json
│   └── schema-index.json
├── locales/
│   ├── en.default.json
│   ├── en.default.schema.json
│   ├── fr.json
│   ├── fr.schema.json
│   ├── de.json
│   └── ...
└── metadata.json
Each directory has a specific role and a strict naming convention. The storefront’s renderer and AssetLoader cache all files in this tree with a 12-hour TTL keyed by absolute path.

Top-level directories

DirectoryPurposeFile types
assets/Static resources served at a public URL.css, .js, images, fonts
blocks/Reusable block templates (Aqua/Liquid).aqua / .liquid
sections/Page sections + section group manifests.aqua, .liquid, *-group.json
snippets/Reusable Aqua includes (rendered via {% render 'name' %}).aqua / .liquid
templates/Page templates that map URLs to compositions of sections.json, .aqua / .liquid
layout/Top-level HTML wrappers (<html>, <head>, <body>).aqua / .liquid
config/Theme-level settings (schema + merchant values + index).json
locales/Translation files for storefront copy and schema strings.json
There is no partials/, views/, or components/. Anything reusable lives under snippets/ or blocks/. The renderer ignores any files or directories not listed above.

assets/

The assets/ folder holds all static files that the browser fetches directly: stylesheets, JavaScript bundles, web fonts, images, SVG icons, and anything else that isn’t a Liquid template.

URL pattern

Each file is served at:
https://<store-domain>/themes/<themeId>/assets/<filename>
…but in templates you should never hardcode that path. Always use the asset_url filter, which produces a cache-busted absolute URL:
<link rel="stylesheet" href="{{ 'application.css' | asset_url }}">
<script src="{{ 'theme.js' | asset_url }}" defer></script>
<img src="{{ 'logo.svg' | asset_url }}" alt="Logo">
The renderer appends a content-hash query parameter so updated assets bypass the CDN cache without the merchant needing to rename the file.

File types served

  • CSS / JS: served as-is with the correct MIME type.
  • Images (.png, .jpg, .webp, .avif, .svg): served raw. For responsive images, prefer image_url / img_url on uploaded images rather than asset images, because Next.js Image Optimization only runs on uploaded resources.
  • Fonts (.woff, .woff2, .ttf): served with long-lived cache headers. Reference via @font-face or use font_face filter on font_picker settings.

Caching and versioning

Asset files are:
  1. Read once into memory via AssetLoader.read() (12-hour LRU cache, 100k entries).
  2. Served at the edge with Cache-Control: public, max-age=31536000, immutable once a content hash is appended by asset_url.
  3. Invalidated automatically when the theme is republished — the AssetLoader.invalidateTheme(themeId) call clears every cache entry prefixed with the theme directory.

What does not go here

  • .liquid / .aqua files — those belong in sections/, snippets/, blocks/, templates/, or layout/. The renderer will not pick up templates in assets/.
  • JSON config — that’s config/.
  • Merchant-uploaded media — product images, blog post images, etc. are uploaded to LaunchMyStore’s media library, not the theme.

blocks/

Theme blocks are reusable, schema-driven content units that a merchant can drop into any section that accepts them. They are the building blocks of the new JSON section model. Each file is a single Aqua/Liquid template plus an optional {% schema %} tag.

Naming

FileBehavior
text.aquaPublic block type text — appears in the editor’s block picker.
_text.aquaPrivate block (underscore prefix). Used internally by sections; not surfaced in the picker.
image.aquaPublic block type image.
_product-media-gallery.aquaPrivate; consumed by product-information section.

Example

{# blocks/button.aqua #}
<a
  href="{{ block.settings.link }}"
  class="btn btn--{{ block.settings.style }}"
  {{ block.lms_attributes }}
>
  {{ block.settings.label }}
</a>

{% schema %}
{
  "name": "t:names.button",
  "settings": [
    { "type": "text",   "id": "label", "label": "Label", "default": "Shop now" },
    { "type": "url",    "id": "link",  "label": "Link" },
    {
      "type": "select", "id": "style", "label": "Style",
      "options": [
        { "value": "primary",   "label": "Primary" },
        { "value": "secondary", "label": "Secondary" }
      ],
      "default": "primary"
    }
  ]
}
{% endschema %}
Blocks can also be nested. Inside a section block loop, {% content_for 'blocks' %} renders the merchant’s configured children in order. See Templates and sections for the full block composition model.

sections/

Sections are the largest reusable unit in a theme. Each .aqua / .liquid file under sections/ is one section that can be referenced from a JSON template, a section group, or a layout’s {% sections '...' %} tag.

File types

FileType
featured-product.aquaSection template + schema
header.aquaSection template + schema
header-group.jsonSection group manifest
footer-group.jsonSection group manifest
_blocks.aquaPrivate helper section
A section group JSON file declares an ordered list of sections that render together — typically the header bar, announcement bar, and meganav for the top of every page; or the footer columns and copyright row for the bottom.
{
  "type": "footer",
  "name": "Footer",
  "sections": {
    "newsletter_RqxiU3": {
      "type": "media-with-content",
      "blocks": { ... }
    },
    "footer_main": {
      "type": "footer",
      "blocks": { ... }
    }
  },
  "order": ["newsletter_RqxiU3", "footer_main"]
}
The layout file references the group by name:
<div id="header-group">
  {% sections 'header-group' %}
</div>

{{ content_for_layout }}

<div id="footer-group">
  {% sections 'footer-group' %}
</div>
The renderer resolves 'header-group'sections/header-group.json, iterates order, and renders each section in turn.

Section schema

Every section file may include a {% schema %} block defining its settings, block whitelist, presets, and template restrictions. See Section Schema for the full reference. The schema is parsed once at install time, cached in AssetLoader.schemaCache as a frozen object, and reused for every render.

snippets/

Snippets are partials — small, reusable Liquid/Aqua fragments rendered via {% render 'name' %} or the legacy {% include 'name' %}.
{# snippets/price.aqua #}
{% liquid
  assign compare = product.compare_at_price
  assign current = product.price
%}
<span class="price">
  {{ current | money }}
  {%- if compare > current -%}
    <s class="price--compare">{{ compare | money }}</s>
  {%- endif -%}
</span>
Used like:
{% render 'price', product: featured_product %}
Variables passed via with / for / inline arguments are scoped to the snippet. Snippets have no schema — they’re pure rendering primitives.
Snippets do not have {% schema %} blocks. If you want merchant-editable settings, use a block (blocks/) or a section (sections/) instead.

App-provided snippets

When apps are installed, their snippets/ directory is mounted into the storefront’s snippet lookup path. A {% render 'app-snippet-name' %} call in a theme will resolve to the app’s snippet if the theme doesn’t provide its own with the same name. See AssetLoader.getExtensionRoots() for the resolution order.

templates/

Templates define what renders for a given URL. Each template file maps to exactly one page type.

Required templates

TemplateURL patternGlobal object
index.json/shop
product.json/products/<handle>product
collection.json/collections/<handle>collection
cart.json/cartcart
page.json/pages/<handle>page
blog.json/blogs/<handle>blog
article.json/blogs/<blog>/<article>article
search.json/searchsearch
404.json(any unrecognized path)
password.json/password (when store is locked)shop
list-collections.json/collectionscollections

Per-handle variants

Any template can have alternate versions selected per resource. The selector is the handle, used in the URL or specified in the resource’s metadata:
templates/product.alternate.json     → ?view=alternate or product.template=alternate
templates/page.contact.json          → /pages/contact (handle "contact")
templates/collection.featured.json   → /collections/<handle> with template "featured"

Resolution order

For a URL like /products/blue-shirt where the product has template: "alternate":
  1. templates/product.alternate.json — most specific (handle + per-handle)
  2. templates/product.alternate.aqua / .liquid — alternate, Liquid form
  3. templates/product.json — default JSON template
  4. templates/product.aqua / .liquid — default, Liquid form
  5. Render fails if none exist.
The renderer tries each in order and uses the first one that exists.

JSON template shape

JSON templates declare a composition of sections:
{
  "sections": {
    "main": {
      "type": "product-information",
      "settings": { "show_vendor": true }
    },
    "recommendations": {
      "type": "product-recommendations",
      "settings": { "products_to_show": 4 }
    }
  },
  "order": ["main", "recommendations"]
}
See Templates and sections for the full schema.

Liquid template fallback

A theme can also provide a plain Liquid/Aqua template (e.g. templates/gift_card.aqua). The renderer treats the whole file as one template, evaluates it with the page’s global object in scope, and wraps the result in the layout (unless {% layout none %} is present).

layout/

Layouts wrap the rendered page body in an HTML shell (<html>, <head>, <body>). Every page is rendered into a layout unless explicitly bypassed.
FileWhen used
theme.aquaDefault — used for every page that doesn’t override.
password.aquaSelected when the template config has "layout":"password".
<name>.aquaAny custom layout selected via "layout": "<name>" in a JSON template or {% layout '<name>' %} in a Liquid template.
A layout must include two magic variables:
  • {{ content_for_header }} — analytics, meta tags, scripts injected by the platform. Place inside <head>.
  • {{ content_for_layout }} — the rendered template body. Place inside <body>.
See Layouts for the full contract.

config/

Holds theme-level (non-section) configuration.
FilePurpose
settings_schema.jsonSchema for the Theme settings screen (colors, typography, layout).
settings_data.jsonMerchant-chosen values for the above schema. Updated by the theme editor.
schema-index.jsonComputed index of every section/block schema in the theme. Generated at install.
installation-manifest.json(Optional) Diagnostic record of the install process. Safe to delete.
settings_schema.json follows the same input-setting format as section schemas — see Input Settings. Merchant values are exposed in Aqua as the global {{ settings.* }}:
<body class="page-width-{{ settings.page_width }}">
  <style>
    :root {
      --color-primary: {{ settings.color_primary }};
      --font-body--family: {{ settings.body_font.family }};
    }
  </style>
schema-index.json is a build artifact — do not edit by hand. The storefront uses it to find sections and blocks by type without scanning the filesystem on every render.

locales/

Translation files for every string the theme renders, plus every schema label shown in the editor.
FilePurpose
en.default.jsonStorefront copy (button labels, error messages, ARIA, etc.). The .default suffix marks the fallback language.
en.default.schema.jsonSchema labels for the editor (section names, setting labels).
fr.jsonStorefront copy translations for French.
fr.schema.jsonSchema label translations for French.
de.json / de.schema.jsonSame for German.
Two access patterns:
  • In templates: {{ 'foo.bar' | t }} looks up the active locale.
  • In schemas: "label": "t:names.button" is resolved to the merchant’s active editor locale at install time and at render time.
See Locales for the full I18n model.

metadata.json (optional)

Top-level theme metadata — name, version, author, support URL. Used by the admin theme library and marketplace listing. Optional but recommended:
{
  "name": "Symmetry",
  "version": "2.0.4",
  "author": "Acme Themes Co.",
  "documentation_url": "https://example.com/docs",
  "support_url": "https://example.com/support"
}

File-naming summary

PatternMeans
<name>.aquaPublic Aqua/Liquid template
<name>.liquidSame as .aqua (extensions are interchangeable)
_<name>.aquaPrivate — not exposed in the editor’s section/block picker
<name>-group.jsonSection group manifest (in sections/)
<type>.<handle>.jsonPer-handle template variant (in templates/)
<lang>.jsonStorefront locale file
<lang>.schema.jsonSchema label locale file
<lang>.default.jsonFallback locale (only one per theme)

What the installer does

When a merchant uploads theme.zip:
  1. The ZIP is unpacked into public/themes/<themeId>/.
  2. The installer scans sections/, blocks/, and templates/ for {% schema %} blocks and writes a flat lookup table to config/schema-index.json.
  3. locales/<lang>.default.json is flattened and merged into the index so t: keys resolve at render time.
  4. settings_data.json is initialized from settings_schema.json defaults if no merchant values exist.
  5. The asset cache is warmed for the most-requested file paths.
After install, every file under the theme directory is treated as read-only at runtime. Changes only happen via:
  • The theme editor (writes back to settings_data.json and template JSON).
  • A new theme upload (creates a new themeId, leaving the old one intact).
  • A direct file-system patch + AssetLoader.invalidateTheme(themeId) (for development workflows only).

Next steps

Templates and sections

How JSON templates compose sections and blocks.

Layouts

The theme.aqua shell, content_for_*, and custom layouts.

Locales

Translating storefront copy and schema strings.

Schema

Defining section and block settings.