ThemeManifest
The theme.json schema — identity, catalog metadata, the compatibility gate, and the inline ThemeSpec the car paints.
The full schema for a theme's theme.json — the durable identity +
catalog metadata, plus the inline spec the
car paints. It mirrors MiniAppManifest
field-for-field where the concept overlaps, with two deliberate
differences:
- No
url— a theme is not a WebView. Instead an inlinespec, so a catalog tile can render a palette preview without downloading the bundle. - No
permissions/privileged/safeWhileDriving— a theme only paints; it has no host capability or driving surface.
Minimal valid manifest
{
"id": "midnight-neon",
"name": { "en": "Midnight Neon", "ar": "نيون منتصف الليل" },
"icon": "./icon.png",
"version": "1.0.0",
"category": "neon",
"spec": {
"schema": 1,
"brightness": "dark",
"colors": {
"background": "#07070D",
"surfaceLow": "#0F1018",
"surfaceContainer": "#13141C",
"surfaceHigh": "#1A1C26",
"outline": "#4B5064",
"outlineVariant": "#24262F",
"onSurface": "#F3F4F8",
"onSurfaceVariant": "#8A90A4",
"accent": "#22D3A8",
"secondary": "#5B8CFF",
"error": "#E76F51"
}
}
}That is the floor: identity, one icon, a category, and a spec whose
colors carries all 8 surfaces plus accent/secondary/error.
Field-by-field
Identity
| Field | Required | Notes |
|---|---|---|
id | yes | URL-safe, 2–64 chars, lowercase alphanumeric / - / _, must not start with a separator (^[a-z0-9][a-z0-9_-]{1,63}$). Globally unique and immutable — the car persists it as the active-theme selection. Never rotate post-publish; bump version instead. |
version | yes | Opaque, semver-shaped by convention. Bump per release to bust the bundle/CDN cache. |
minHostVersion | no | Cars below this version hide the theme. Omit for "any". |
Display
| Field | Required | Notes |
|---|---|---|
name | yes | Locale map — at least one entry. Catalog renders the tile title from it. Fallback: requested locale → en → first entry. |
description | no | Same locale-map shape as name. UI degrades gracefully when absent. |
icon | yes | Bundle-relative path (./...), PNG or SVG, 256×256, ≤ 100 KB. Publish rewrites to a CDN URL. |
coverImage | no | 16:9 cover for the detail surface. PNG / JPEG / WebP / SVG, 1280×720, ≤ 500 KB. |
screenshots | no | Up to 8 images. PNG / JPEG / WebP / SVG, ≤ 1920×1080, ≤ 800 KB each. Order is publisher-chosen. |
Catalog placement
| Field | Required | Notes |
|---|---|---|
category | yes | Closed enum of 11 slugs — see Categories. Drives the catalog grouping. |
tags | no | Free-form, lowercase alphanumeric + hyphens (^[a-z0-9-]+$), ≤ 24 chars each, ≤ 8 tags. Search/filter only. |
Compatibility + spec
| Field | Required | Notes |
|---|---|---|
requires | no | Hard car/host compatibility requirements. Reuses the mini-app requires block verbatim, so evaluateCompatibility() applies the same gate — the catalog hides, and the car refuses to apply, an incompatible theme. Omit for "applies to any car". |
spec | yes | The inline ThemeSpec design-token document — the payload the car actually paints. Embedded in the catalog row so a tile previews the palette without the bundle. |
Catalog wire shape
When the backend serves a theme it adds a distribution envelope to each
manifest (asset paths already rewritten to CDN URLs), mirroring the
mini-app catalog entry. The catalog body is raw (not wrapped in a
{ success, data } envelope):
// GET /api/v1/themes (public)
// GET /api/v1/themes/me (authenticated; includes the user's beta themes)
{
"themes": [
{
/* ...all ThemeManifest fields, asset paths rewritten to CDN URLs... */
"bundleUrl": "https://cdn.i99dash.app/themes/midnight-neon/1.0.0/bundle.tar.gz",
"bundleSha256": "<64 hex>",
"track": "production",
"releaseNotes": null
}
]
}Query params mirror mini-apps: limit (1–200, default 50), offset
(≥ 0), category (optional slug). The endpoint supports If-None-Match
ETag+304.
Related
ThemeSpecreference — the design tokens insidespec.- Building a theme — the authoring + publish walkthrough.
- Categories — the closed
categoryenum. MiniAppManifest— the mini-app manifest this mirrors.