NUMU Docs
Contact
APIThemes
Partner Apps
APIThemes
Partner Apps
  1. Theme engine
  • 🗂️ Themes overview
  • 🚀 Start here
  • Glossary
  • Theme engine
    • Architecture
    • BYOT contract
    • Customizer
    • Federation runtime
    • Page data contract
    • Theme manifest
  • Theme SDK
    • Components
    • Federation helpers
    • Hooks
    • SDK overview
    • Type definitions
  • CLI & Vite plugin
    • CLI commands
    • CLI overview
    • Lint rules
    • Section library
    • Vite plugin
  • Storefront host
    • API proxies
    • Built-in fallbacks
    • BYOT fork
    • Routing
    • Storefront overview
  1. Theme engine

Theme manifest

theme.json — the top-level descriptor every theme ships.

Minimum#

{
  "id": "my-theme",
  "name": { "en": "My Theme" },
  "version": "1.0.0",
  "author": "Your Name <you@example.com>"
}
This passes the CLI's 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.

Full surface#

{
  "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-by-field#

Identity#

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

Templates (presets)#

presets.templates.<page_type> defines a starter layout the customizer applies when a merchant first installs your theme.
Supported page types:
home, product, collection, cart,
account, account_login, account_register, account_orders,
search, page, policies, blogs, blog, article,
404, password
Each template carries:
{
  "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#

Theme-level color/typography variants the customizer exposes as a top-level switcher. Each variant is a partial settings override applied on top of the resolved theme settings.
{
  "variants": [
    { "name": "Light", "settings": { "color_bg": "#FFFFFF" } },
    { "name": "Dark",  "settings": { "color_bg": "#0F172A" } }
  ]
}
Themes that don't define variants get a single "Default" variant under the hood.

Error + loading templates#

Two static-HTML files referenced by URL. Used by the storefront's 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"
}
Phase 7.3 added these. If omitted, the storefront falls back to its built-in (unbranded) error/loading pages. See numu-theme-cli/src/commands/init.ts for the scaffolded HTML.

How the storefront reads the manifest#

The manifest itself isn't loaded by the storefront on every request — only the external_theme.bundle_url is. But certain manifest fields propagate:
1.
presets — Applied once when the merchant installs/re-installs the theme. After that, the merchant's customizer state is the source of truth.
2.
error_template_url + loading_template_url — SSR-fetched (cached aggressively) by the storefront's error.tsx / loading.tsx.
3.
Everything else — Used by the customizer + marketplace UI in the hub.

Versioning + breaking changes#

Bumping version:
Patch (1.0.0 → 1.0.1): no schema changes. Merchants auto-update on next theme refresh.
Minor (1.0.0 → 1.1.0): new schema fields with defaults. Backward-compatible.
Major (1.0.0 → 2.0.0): removed/renamed schema fields. Merchants stay on the old major until they explicitly upgrade — the customizer will warn before applying.
The marketplace stores every version. Merchants can pin to any past version via the hub's Themes panel.

Validation#

numu-theme lint's manifest-required-fields rule checks:
id, name, version, author all present
id is kebab-case (^[a-z0-9-]+$)
version is valid semver
name is a non-empty object with at least en
Marketplace submission additionally verifies:
id doesn't clash with a published theme owned by a different developer
version is strictly greater than the last submitted version for this id
All paths in screenshots, error_template_url, loading_template_url exist in the submitted ZIP
The full submission pipeline is documented in Theme Dev E2E.
Modified at 2026-09-24 13:03:12
Previous
Page data contract
Next
Components
Built with