Theme contract and rendering
The generated schemas in each theme repository are the exact contract for its manifest and render context. Read them before editing templates or generating theme code.
Format versions and slots
Section titled “Format versions and slots”Format v1 themes have six slots and remain compatible with feed and item pages. Format v2 adds standalone Pages and public Search, for eight slots total:
| Slot | Purpose |
|---|---|
webFeed |
Render the public feed home page. |
webItem |
Render one item page. |
webPage |
Render a standalone Page, including the editable default 404 Page. |
webSearch |
Render the complete /search/ results page. |
webHeader |
Insert markup before </head>. |
webBodyStart |
Insert markup immediately after <body>. |
webBodyEnd |
Insert markup immediately before </body>. |
rssStylesheet |
Provide the complete RSS XSL stylesheet. |
An active format v1 theme prevents a Page from becoming Published or Unlisted. It does not block Page drafts. Use format v2 for Pages, navigation, and public search.
Manifest and render context
Section titled “Manifest and render context”microfeed-theme.json declares package identity, semantic version, compatible
microfeed versions, the theme file paths, and optional assets. Its optional
description is a short, public summary of what the theme is good for and is
limited to 280 characters. One package ID and version identifies one immutable
set of content.
A theme may also declare one previewFixture, such as
fixtures/editorial.json. The value must be a safe relative path to a UTF-8
JSON object no larger than 128 KiB. Admin uses this theme-specific fixture as
the default demo dataset while still allowing the owner to switch to the
current site’s published content. A declared fixture is stored independently
from templates and assets, participates in the immutable package checksum, and
survives installation, Admin-derived drafts, publication, theme init, and
theme export. Admin drafts inherit it but cannot edit it. Themes without a
declared fixture continue previewing current-site data.
A format v2 manifest may also declare searchItemDestination to control where
item results open in both the popup and /search/:
| Value | Item destination and feed field |
|---|---|
web |
The local microfeed item page from JSON Feed items[]._microfeed.web_url. RSS uses it as <item><link> only when Item URL is empty. |
url |
The custom Item URL from JSON Feed items[].url and RSS <item><link>. |
attachment |
The media attachment from JSON Feed items[].attachments[0].url and RSS <item><enclosure url="…">. |
Omission is equivalent to web. When the selected custom value is missing,
microfeed falls back to the local item page; Page results always keep their
local Page URL.
The render context begins with the public JSON Feed and adds:
current_year;_theme.package_id;_theme.version;_theme.asset_base_url;navigation_pageson public HTML views;pageon the Page slot; andsearchon the Search slot.
On item pages, use items.0. The item alias remains available only for
compatibility with older themes.
Mustache remains logicless: variables, escaped and unescaped values, sections,
inverted sections, and iteration are supported. Owner-authored rich HTML such
as item or Page content_html is intended for triple-brace rendering. Escape
ordinary text and attributes by default.
Render Pages and navigation
Section titled “Render Pages and navigation”navigation_pages contains Published Pages whose Show in navigation
setting is enabled, in the order selected in Admin. Draft, Unlisted, and the
special 404 Page are excluded. Navigation is website-only and never adds a
Page to RSS or JSON Feed.
Render the same navigation in feed, item, Page, and Search templates:
<nav aria-label="Site navigation"> {{#navigation_pages}} <a href="{{url}}">{{navigation_label}}</a> {{/navigation_pages}}</nav>The webPage slot also renders the protected default 404 Page. Check
page.is_not_found_page only when the theme needs distinct 404 styling; the
ordinary Page structure should otherwise work unchanged.
Connect public search
Section titled “Connect public search”microfeed injects one accessible search dialog into every public HTML page and
owns its keyboard handling, request cancellation, safe result rendering, and
typeahead behavior. Do not copy that dialog or script into the theme. Add
data-microfeed-search-open to a theme control that should open it:
<button type="button" aria-haspopup="dialog" aria-controls="microfeed-search-dialog" data-microfeed-search-open> Search</button>The webSearch slot owns the surrounding layout for /search/. Use
search.query and the stable input and results hooks:
<form action="/search/" method="get" role="search"> <label for="site-search">Search this site</label> <input id="site-search" name="q" type="search" value="{{search.query}}" data-microfeed-search-input > <button type="submit">Search</button> <div aria-live="polite" data-microfeed-search-results data-microfeed-search-details ></div></form>The optional details hook adds a short date and excerpt to each result. The
dialog and results use stable data-microfeed-search-* hooks and
mf-public-search* classes for targeted styling.
Every rendered result exposes title, type, destination hostname, and optional details elements. The hostname comes from the resolved link destination and is hidden by default. Enable it for Item results and customize the two result surfaces independently in the Web header:
[data-microfeed-search-result-type="item"] { --mf-search-result-domain-display: inline;}
[data-microfeed-search-results-context="popup"] .mf-public-search-result { padding: 0.6rem;}
[data-microfeed-search-results-context="page"] .mf-public-search-result { display: grid; grid-template-columns: minmax(0, 1fr) auto;}
.mf-public-search-result__domain { color: var(--mf-muted);}Use .mf-public-search-result__title, __type, __domain, and __details
for individual parts. The result link also has
data-microfeed-search-result-type="item|page"; its container has
data-microfeed-search-results-context="popup|page". Keep the supplied link
and text nodes instead of reconstructing untrusted result HTML.
Define --mf-accent, --mf-background, --mf-surface, --mf-text,
--mf-muted, and --mf-border color tokens so the injected dialog follows the
theme. Search preview uses representative results because live search is not
available inside the isolated preview.
Schemas, fixtures, and package limits
Section titled “Schemas, fixtures, and package limits”A generated repository includes JSON Schemas under .microfeed/schemas/, a
representative package fixture, and built-in test fixtures for empty, minimal,
rich, paginated, media-heavy, missing-optional, multi-author, and hostile-rich-
HTML content.
Package limits are:
- 128 KiB per text slot;
- 512 KiB total theme text;
- 128 KiB for the optional declared preview fixture;
- 100 declared assets;
- 5 MiB per asset; and
- 20 MiB total assets.
Validation rejects absolute paths, traversal, symlinks, missing or malformed declared preview fixtures, undeclared files, malformed Mustache, invalid semantic versions, incompatible microfeed ranges, and unsupported asset types. Tests check deterministic rendering, HTML structure, and valid RSS XSL; themes are trusted code, so they do not sanitize intentional HTML or JavaScript.
Continue with Bundle CSS, JavaScript, and assets or return to Build and release a theme.

