Theme Manifest
The theme.json manifest format — every field, what it controls, and how Blog-Doc uses it.
Every theme must include a theme.json file at its root. This is how Blog-Doc identifies the theme, activates features, and knows which menu locations to expose in the admin.
Full example
{
"name": "My Theme",
"id": "my-theme",
"version": "1.0.0",
"features": ["blog"],
"menus": ["primary", "footer"],
"screenshot": "screenshot.png"
}
Fields
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Human-readable display name shown in the admin |
id |
string | Yes | Identifier — must match the theme folder name, [a-z0-9-] only |
version |
string | Yes | Semantic version string (e.g. "1.0.0") — used for update detection |
features |
array | No | Feature flags that activate additional routes |
menus |
array | No | Menu location names this theme renders |
screenshot |
string | No | Preview — used for theme card |
features
The only currently supported feature value is "blog". Declaring it activates the blog routes:
/blog— paginated blog index/blog/:slug— single post/blog/page/:num— paginated continuation/categories/:category— category index (requirestaxonomy.html)/tags/:tag— tag index (requirestaxonomy.html)
A theme with "features": [] or no features key is a pages-only theme — no blog routes are registered.
menus
Declares which menu location names the theme renders. These names appear in the Menus admin editor as editable locations. If you declare ["primary", "footer"], two menu slots appear in the admin.
In templates the menus are accessed via the menus object:
{{#each menus.primary}}
<a href="{{ url }}">{{ label }}</a>
{{/each}}
If a menu location hasn't been populated in the admin, the array is empty — always guard with {{#if menus.primary | length}}.
Note: To avoid menu name collisions and accidental overwriting, prefix menu names with the theme
id. For example, use"default-primary"and"default-footer"instead of generic names like"primary"or"footer". Then reference the prefixed menu in your template, for example:{{#each menus["default-primary"]}}.
id and the folder name
The theme id must exactly match its folder name inside app/themes/. If they diverge, Blog-Doc can't reliably resolve the theme on activation. The admin install endpoint sanitizes the ZIP's manifest id to [a-z0-9-] before writing to disk.
version and update detection
The marketplace compares each remote theme's version against the locally installed copy with the same id. If the remote version differs, an Update available badge appears in the admin and the install-remote endpoint replaces the local copy.
screenshot
The screenshot file is separate from theme.json but equally required. Accepted extensions: .jpg, .jpeg, .png, .gif, .webp, .svg, .avif. Blog-Doc uses it for the theme card in both the Installed and Marketplace tabs. Without a screenshot, the theme card shows a blank grey placeholder.
Recommended size: 1280 × 960px or any 4:3 aspect ratio at a similar resolution.