Themes

First-class storefront design. A tenant can hold many themes, swap between them, fork drafts, and ship custom CSS escape hatches.

{
  "theme": {
    "id": "308...",
    "handle": "noir",
    "name": "Noir",
    "tokens": {
      "--background": "#0a0a0a",
      "--foreground": "#fafafa",
      "--primary": "#fff",
      "--primary-foreground": "#000",
      "--radius": "0.5rem",
      "--ring": "#fafafa"
    },
    "font_body": "Geist",
    "font_heading": "Geist",
    "font_url_imports": ["https://fonts.googleapis.com/..."],
    "layout_density": "comfortable",
    "custom_css": "/* escape hatch */",
    "logo_url": "https://...",
    "logo_dark_url": "https://...",
    "favicon_url": "https://...",
    "is_active": true,
    "parent_theme_id": null
  }
}

Tokens

tokens is a free-form JSONB map of CSS variable names to values. The shadcn convention (--background, --foreground, --primary, --radius, ...) is recommended but not enforced. The storefront just emits a :root { ... } block from these.

One active theme at a time

A partial unique index enforces (account_id) WHERE is_active = TRUE. Activating a theme atomically deactivates the previous active one.

curl -X POST "$API/themes/{theme_id}/activate.json" \
  -H "Authorization: Bearer $NEVIOS_KEY"

Drafts (parent theme)

Fork the live theme into a draft, edit the draft, then activate-swap:

# Fork the active theme
curl -X POST "$API/themes.json" \
  -H "Authorization: Bearer $NEVIOS_KEY" -H "Content-Type: application/json" \
  -d '{
    "handle": "noir-v2",
    "name": "Noir v2 (draft)",
    "parent_theme_id": 308...,
    "tokens": { ... new values ... },
    "is_active": false
  }'

# Test in dashboard preview...

# When ready, activate
curl -X POST "$API/themes/{new_theme_id}/activate.json" \
  -H "Authorization: Bearer $NEVIOS_KEY"

The previous theme stays in the database (not archived) — easy to roll back.

Custom CSS escape hatch

For one-off design tweaks beyond what tokens express:

{
  "custom_css": ".product-card .price-tag { font-weight: 800; }"
}

Loaded after tokens. No sandboxing yet — staff-only authoring.

Endpoints

POST   /admin/2026-01/{handle}/themes.json
GET    /admin/2026-01/{handle}/themes.json
GET    /admin/2026-01/{handle}/themes/active.json
PUT    /admin/2026-01/{handle}/themes/{id}.json
POST   /admin/2026-01/{handle}/themes/{id}/activate.json
DELETE /admin/2026-01/{handle}/themes/{id}.json

Cannot archive the currently-active theme — 409. Activate another first.

Next: Email templates.