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

BYOT contract

The exact surface every NUMU theme must expose for the federation runtime to load it. If your theme deviates from this, builds fail at the CLI level (numu-theme build → contract validation) before you even try to install.

The five required fields#

your-theme/
ā”œā”€ā”€ theme.json                   ← (1) manifest
ā”œā”€ā”€ src/main.tsx                 ← (2) entry exporting `mount`
ā”œā”€ā”€ settings_schema.json         ← (3) global settings declarations
ā”œā”€ā”€ schemas/sections/<type>.json ← (4) one per section type referenced
└── locales/en.default.json      ← (5) at least the default locale

1. theme.json — top-level manifest#

{
  "id": "my-fashion-theme",
  "name": { "en": "My Fashion Theme", "ar": "Ų«ŁŠŁ… Ų§Ł„Ł…ŁˆŲ¶Ų©" },
  "version": "1.0.0",
  "author": "Your Name <you@example.com>",
  "min_sdk_version": "0.6.0",
  "presets": {
    "templates": {
      "home": { "name": "Home", "sections": {...}, "order": [...] },
      "product": { ... },
      "collection": { ... },
      "cart": { ... }
    }
  },
  "error_template_url": "dist/error.html",
  "loading_template_url": "dist/loading.html"
}
Validated by numu-theme lint's manifest-required-fields rule. The minimum:
id — kebab-case slug, unique on the marketplace
name — bilingual object {en, ar?}
version — semver
author — string
Optional but recommended:
presets.templates — starter section layouts the customizer shows on first install
error_template_url / loading_template_url — static HTML used by storefront error.tsx / loading.tsx (which load before the SDK federation runtime)

2. src/main.tsx — entry#

Must export a mount function with this signature:
import type { MountContext } from "@numueg/theme-sdk";

export function mount(ctx: MountContext): () => void {
  // create root, render
  // return cleanup function
}
Contract validator (numu-theme-plugin/src/lib/contract-validate.ts) checks:
A mount named export exists
It's a function
React + react-dom + @numueg/theme-sdk are all listed as peerDependencies (i.e. externalized)
A complete main.tsx:
import { createRoot } from "react-dom/client";
import { NuMuProvider } from "@numueg/theme-sdk";
import type { MountContext } from "@numueg/theme-sdk";

import Home from "./pages/Home";
import Product from "./pages/Product";
import Cart from "./pages/Cart";
import NotFound from "./pages/NotFound";

function dispatch(page: MountContext["page"]) {
  switch (page.type) {
    case "home":     return <Home />;
    case "product":  return <Product product={page.data?.product} />;
    case "cart":     return <Cart />;
    case "404":      return <NotFound />;
    default:         return null;
  }
}

export function mount(ctx: MountContext) {
  const el = document.getElementById("numu-root")!;
  const root = createRoot(el);
  root.render(
    <NuMuProvider
      store={ctx.store}
      themeSettings={ctx.themeSettings}
      page={ctx.page}
      locale={ctx.locale}
      direction={ctx.direction}
      currency={ctx.currency}
    >
      {dispatch(ctx.page)}
    </NuMuProvider>
  );
  return () => root.unmount();
}

3. settings_schema.json — global theme settings#

{
  "settings": [
    {
      "type": "color",
      "id": "color_primary",
      "label": "Primary color",
      "default": "#0F172A"
    },
    {
      "type": "font_picker",
      "id": "font_heading",
      "label": "Heading font",
      "default": "Inter"
    },
    {
      "type": "select",
      "id": "header_layout",
      "label": "Header layout",
      "default": "logo-right",
      "options": [
        { "value": "logo-right",  "label": "Logo right" },
        { "value": "logo-center", "label": "Logo center" },
        { "value": "logo-left",   "label": "Logo left" },
        { "value": "stacked",     "label": "Stacked" }
      ]
    }
  ]
}
These show up in the customizer's "Theme settings" panel. Resolved values arrive on ctx.themeSettings at mount.
Full list of input types: see Customizer.

4. schemas/sections/<type>.json — per-section schema#

Every section type referenced in theme.json presets OR added via the customizer's Add-Section dialog needs a schema file. The schema declares settings (per-section), blocks (children), and presets (Add-Section variants).
{
  "type": "hero",
  "name": "Hero",
  "locales": { "ar": { "name": "البانر Ų§Ł„Ų±Ų¦ŁŠŲ³ŁŠ" } },
  "settings": [
    { "type": "text", "id": "headline", "label": "Headline", "default": "Welcome" },
    { "type": "image_picker", "id": "background_image", "label": "Background" },
    { "type": "select", "id": "alignment", "label": "Alignment",
      "default": "center",
      "options": [
        { "value": "left",   "label": "Left" },
        { "value": "center", "label": "Center" },
        { "value": "right",  "label": "Right" }
      ]
    }
  ],
  "blocks": [
    { "type": "cta_button", "name": "CTA Button", "limit": 2, "settings": [...] }
  ],
  "presets": [
    { "name": "Hero with button", "settings": {...}, "blocks": [...] },
    { "name": "Hero text-only",   "settings": {...} }
  ]
}
The CLI plugin runs schema-codegen to generate TypeScript types into src/__generated__/sections.d.ts — your section components get typed settings automatically:
// auto-generated
export interface HeroSettings {
  headline?: string;
  background_image?: string;
  alignment?: "left" | "center" | "right";
}
// your section
import type { HeroSettings } from "../__generated__/sections";

export default function Hero({ settings }: { settings: HeroSettings }) {
  return <section>{settings.headline}</section>;
}
Lint rule schema-registry-sync flags every section referenced in presets that's missing a schema (and vice versa).

5. locales/en.default.json — translations#

{
  "cart": {
    "title": "Your cart",
    "empty": "Your cart is empty",
    "checkout": "Checkout"
  },
  "product": {
    "addToCart": "Add to cart",
    "soldOut": "Sold out"
  }
}
Consumed via useTranslation() in the SDK:
import { useTranslation } from "@numueg/theme-sdk";
const t = useTranslation();
return <button>{t("product.addToCart")}</button>;
locales/ar.json (and others) override per key. Lint rule locale-parity flags missing keys in non-default locales.

Optional fields#

FilePurpose
schemas/blocks/<type>.jsonReusable blocks across sections
assets/*Static assets; copied to dist/assets/ with content hashes
templates/*.jsonCustomizer-installable templates beyond the presets in theme.json
dist/error.htmlRendered by storefront error.tsx when the SDK fails before mount
dist/loading.htmlRendered by storefront loading.tsx during route transitions

Page types the bundle should handle#

If your theme doesn't handle a page type, the storefront falls back to the built-in renderer. The current type set:
home, product, collection, cart,
checkout_contact, checkout_shipping, checkout_payment,
checkout_review, checkout_processing, checkout_thank_you,
account, account_login, account_register, account_recover, account_reset,
account_profile, account_orders, account_order, account_addresses,
account_gift_cards, account_wishlist,
search, page, policies, blogs, blog, article,
password, 404
You can render null for any type you don't want to own — the built-in fallback takes over for that route only.

What gets validated when#

StageValidatorCatches
numu-theme lint (any time)All 10 rulesMissing schemas, locale gaps, hex literals, useApp without availability guard, etc.
numu-theme dev (HMR)Schema codegen + contract validatorType drift, missing mount export
numu-theme buildContract validator (strict)Missing exports, React not externalized
numu-theme submit (server-side)AST scan + sandboxed re-buildForbidden globals (document.write, raw eval), exfiltration patterns
Admin reviewManual + diff against last versionVisual regression, malicious code
A theme can't reach a customer's browser without passing every gate.
Modified atĀ 2026-09-24 13:03:12
Previous
Architecture
Next
Customizer
Built with