theme.json Schema
Every jekcms theme has a theme.json at its root. This file defines metadata, declares the customizer UI, and optionally seeds default colors and typography. This page is the full reference.
Top-level fields
| Field | Type | Required | Notes | |---|---|---|---| | name | string | yes | Human-readable theme name shown in admin | | slug | string | yes | Must match folder name — lowercase, hyphens only | | version | string | yes | Semver recommended (1.0.0) | | author | string | yes | Author name or organization | | description | string | no | One-line summary for the theme picker | | license | "free" \| "premium" | no | "premium" requires STD+ license to activate | | requires | string | no | Minimum jekcms version, e.g., ">=2.0" | | preview | string | no | Relative path to preview image (e.g., "preview.jpg") | | settings | object | no | Legacy defaults (see below) | | customizer | object | no | Customizer tabs + fields |
Legacy settings block
settings.colors and settings.typography are legacy — they still work for simple themes but new themes should use customizer.tabs instead, which gives admins an editable UI rather than hardcoded values.
{
"settings": {
"colors": {
"primary": "#2563eb",
"text": "#0f172a",
"background": "#ffffff"
},
"typography": {
"body": "Inter, system-ui, sans-serif",
"heading": "Inter, system-ui, sans-serif"
}
}
}
Reach these with theme_setting('colors.primary') from a template.
customizer.tabs structure
Each key under customizer.tabs is a tab. Tab keys should be lowercase, short, and hyphen-free (they're used as the first segment of theme_option('tab.field')).
{
"customizer": {
"tabs": {
"branding": {
"label": "Branding",
"description": "Logo, colors, and voice.",
"icon": "palette",
"fields": { "...": "..." }
}
}
}
}
Per-tab fields
| Field | Type | Notes | |---|---|---| | label | string | Tab title shown in the sidebar | | description | string | Small helper text under the title | | icon | string | Lucide icon name (palette, layout, type, code, etc.) | | fields | object | Field definitions keyed by field slug |
Field types reference
Every field object accepts a common set of keys plus type-specific ones.
Common keys (all types)
| Key | Type | Notes | |---|---|---| | type | string | Required. One of: color, font, range, toggle, select, textarea, code, url, text | | label | string | Field label in the UI | | default | varies | Default value if user hasn't set one | | help | string | Tooltip / helper text under the input | | css_var | string | If set, auto-emits --name: value; in :root |
Type-specific keys
| Type | Extra keys | Default format | |---|---|---| | color | — | "#2563eb" | | font | — | {"family": "Inter", "weight": "400"} | | range | min, max, step, unit | Number (e.g., 1200) | | toggle | — | true / false | | select | options (object: {"key": "Label"}) | Option key string | | textarea | rows | String | | code | language ("css", "html", "js") | String | | url | — | String | | text | maxlength | String |
Full example
A theme with three tabs — branding, layout, advanced:
{
"name": "Ocean",
"slug": "ocean",
"version": "1.0.0",
"author": "jekcms Team",
"description": "A clean coastal theme with a modern typographic scale.",
"license": "free",
"requires": ">=2.0",
"preview": "preview.jpg",
"customizer": {
"tabs": {
"branding": {
"label": "Branding",
"icon": "palette",
"fields": {
"accent": {
"type": "color",
"label": "Accent color",
"default": "#0891b2",
"css_var": "--color-accent",
"help": "Used for links, buttons, and focus rings."
},
"body_font": {
"type": "font",
"label": "Body font",
"default": {"family": "Inter", "weight": "400"},
"css_var": "--font-body"
},
"logo": {
"type": "url",
"label": "Logo URL",
"default": ""
}
}
},
"layout": {
"label": "Layout",
"icon": "layout",
"fields": {
"max_width": {
"type": "range",
"label": "Content max width",
"default": 1200,
"min": 800,
"max": 1600,
"step": 40,
"unit": "px",
"css_var": "--layout-max-width"
},
"sidebar": {
"type": "select",
"label": "Sidebar position",
"default": "right",
"options": {
"left": "Left",
"right": "Right",
"none": "No sidebar"
}
},
"sticky_header": {
"type": "toggle",
"label": "Sticky header",
"default": true
}
}
},
"advanced": {
"label": "Advanced",
"icon": "code",
"fields": {
"custom_css": {
"type": "code",
"language": "css",
"label": "Custom CSS",
"default": ""
},
"head_html": {
"type": "textarea",
"label": "Custom <head> HTML",
"rows": 6,
"default": "",
"help": "Injected before </head>. Use for analytics or verification tags."
}
}
}
}
}
}
Reach values via theme_option('branding.accent'), theme_option('layout.max_width'), etc. Or — for anything with a css_var — just use the emitted variable directly in your CSS.