Getting started
Prerequisites
- Node.js 20+
- Familiarity with Liquid templating
Two ways to build
A theme is just a directory of files in the layout below, zipped. Nothing about the format requires our tooling, and you never have to install anything to ship a theme:
- Build the directory by hand and upload the
.zipon the developer portal. Zip it however you like -zip -r my-theme.zip my-theme/, or Finder’s “Compress”. Bundles that nest everything under a wrapper folder are accepted; the platform flattens them on upload. - Use the
qmCLI if you have it, for a local preview loop and a pre-flight of the review gate. It is a convenience, not a requirement, and it is distributed to partner developers rather than published on the public npm registry - ask us for access if you want it.
Preview locally with the CLI
qm theme dev # local preview server over your sample-data fixtures
qm theme validate # run the review gate's checks before you submitqm theme dev renders your templates through the same engine the platform runs
in production, using the bundled sample-data/*.json fixtures. Open the
printed URL and iterate - edit a section, save, refresh.
Local preview, the marketplace preview, and the automated review gate all call
the identical render function, so a template that renders in qm theme dev
renders in review. Note that this covers the automated checks only - see
what validate does not check.
Preview without the CLI
Once a version is published you can render any template over your own fixtures at the public preview endpoint - no auth, no store:
GET /public/themes/<your-slug>/preview/<template>?version=1.0.0Reviewers use exactly this endpoint against your staged bundle, so it is worth opening each of the seven templates yourself.
Project structure
A theme is a directory with this layout:
my-theme/
├── theme.json # bundle manifest (name, version, preview image)
├── layout/
│ └── theme.liquid # top-level HTML wrapper for every page
├── templates/ # one .json per page type
│ ├── index.json
│ ├── product.json
│ ├── collection.json
│ ├── search.json
│ ├── page.json
│ ├── wishlist.json
│ ├── blog.json
│ ├── article.json
│ └── 404.json
├── sections/ # *.liquid, each with a {% schema %} block
├── snippets/ # reusable Liquid partials (no schema)
├── config/
│ └── settings_schema.json # global merchant-editable settings
├── assets/ # CSS, JS, fonts, images, screenshots
│ ├── common.css # loaded on every page
│ ├── theme.css # design tokens / CSS variables
│ ├── cart.js # add-to-cart + cart UI behaviour
│ └── screenshots/home.png # powers the theme browser tile
├── sample-data/ # dummy data for standalone preview
│ ├── shop.json # REQUIRED
│ ├── products.json # note the plural - an array
│ ├── collections.json # note the plural - an array
│ ├── menus.json # main-menu + footer-menu
│ ├── search.json
│ └── page.json
└── locales/
└── en.default.json # optional translationsRequired files
Every theme needs all of these. A bundle missing any of them cannot pass review:
theme.json- and it must declare anameand aversion. Submission is rejected outright without them, because the slug is derived fromname.layout/theme.liquid- without it no template can render, so all seven preview renders in the gate fail.config/settings_schema.json- must be a JSON array.sample-data/shop.json- the gate renders every template over your fixtures, and refuses to render anything without a shop. See Preview & sample data.- A template for each of the seven preview keys the gate renders:
templates/index.json,product.json,collection.json,search.json,page.json,wishlist.json,404.json.
The last two points are where most first submissions fail. The gate renders
index,product,collection,search,page,wishlistand404- there is no “optional” template among them.qm theme validaterenders the same seven locally, so run it before you submit.
What each piece does
theme.json- the manifest. Name, version, tagline, description, and the preview image + screenshots that render on the marketplace tile.layout/theme.liquid- the single HTML shell. Renders<html>,<head>, the header section,{{ content_for_layout }}(where each page’s sections render), and the footer.templates/*.json- which sections appear on each page type, in what order, with what settings. One file per page type.sections/*.liquid- the building blocks. Each ends with a{% schema %}JSON block declaring its merchant-editable settings and blocks.config/settings_schema.json- global theme settings (brand colors, fonts, logo) the merchant edits once and every page reads.assets/- your CSS, JS, fonts, and images, referenced through theasset_urlfilter.sample-data/*.json- fixtures used only for previews. Never shipped to a real store, but required: the automated review gate renders every template over them, andshop.jsonis mandatory.