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.

Be the first to know

New features, release notes & CMS guides — a couple of emails a month, no spam.