theme.json — the top-level descriptor every theme ships.{
"id": "my-theme",
"name": { "en": "My Theme" },
"version": "1.0.0",
"author": "Your Name <you@example.com>"
}manifest-required-fields lint rule but produces a "blank" theme with no preset templates. The customizer will let merchants build from scratch by adding sections one by one.{
"id": "fashion-pro",
"name": { "en": "Fashion Pro", "ar": "موضة برو" },
"version": "1.2.0",
"author": "ACME Themes <hello@acme.example>",
"description": { "en": "Bold, modern fashion theme with full RTL.", "ar": "..." },
"tags": ["fashion", "rtl", "minimal"],
"min_sdk_version": "0.6.0",
"screenshots": [
"assets/screenshots/home.png",
"assets/screenshots/product.png"
],
"presets": {
"templates": {
"home": {
"name": "Home",
"sections": {
"hero_1": { "type": "hero", "settings": {...}, "blocks": {...}, "block_order": [...] },
"featured_1": { "type": "featured-products", "settings": {...} },
"newsletter_1": { "type": "newsletter-signup", "settings": {...} }
},
"order": ["hero_1", "featured_1", "newsletter_1"]
},
"product": { ... },
"collection": { ... },
"cart": { ... }
}
},
"variants": [
{ "name": "Light", "settings": { "color_bg": "#FFFFFF", "color_fg": "#0F172A" } },
{ "name": "Dark", "settings": { "color_bg": "#0F172A", "color_fg": "#F8FAFC" } }
],
"error_template_url": "dist/error.html",
"loading_template_url": "dist/loading.html"
}| Field | Required | Notes |
|---|---|---|
id | ✅ | Kebab-case, marketplace-unique. Once published, immutable. |
name | ✅ | Bilingual {en, ar?, ...}. Shown in the marketplace + customizer. |
version | ✅ | Semver. Each marketplace submission bumps this. |
author | ✅ | Free-form. Email helps merchants find support. |
description | – | Bilingual short pitch (≤200 chars per locale). |
tags | – | For marketplace search. |
min_sdk_version | – | If set, install fails if the merchant's storefront SDK is older. |
screenshots | – | Up to 8 paths into assets/screenshots/. Marketplace shows them in order. |
presets.templates.<page_type> defines a starter layout the customizer applies when a merchant first installs your theme.home, product, collection, cart,
account, account_login, account_register, account_orders,
search, page, policies, blogs, blog, article,
404, password{
"name": "Display name (shown in template-switcher)",
"sections": {
"<section_id>": SectionInstance
},
"order": ["<section_id>", ...]
}SectionInstance.type must have a matching schemas/sections/<type>.json. The CLI's schema-registry-sync lint catches mismatches.{
"variants": [
{ "name": "Light", "settings": { "color_bg": "#FFFFFF" } },
{ "name": "Dark", "settings": { "color_bg": "#0F172A" } }
]
}error.tsx and loading.tsx which render BEFORE the SDK federation runtime loads, so they can't be React components from the bundle.{
"error_template_url": "dist/error.html",
"loading_template_url": "dist/loading.html"
}numu-theme-cli/src/commands/init.ts for the scaffolded HTML.external_theme.bundle_url is. But certain manifest fields propagate:presets — Applied once when the merchant installs/re-installs the theme. After that, the merchant's customizer state is the source of truth.error_template_url + loading_template_url — SSR-fetched (cached aggressively) by the storefront's error.tsx / loading.tsx.version:numu-theme lint's manifest-required-fields rule checks:id, name, version, author all presentid is kebab-case (^[a-z0-9-]+$)version is valid semvername is a non-empty object with at least enid doesn't clash with a published theme owned by a different developerversion is strictly greater than the last submitted version for this idscreenshots, error_template_url, loading_template_url exist in the submitted ZIP