Manifest schema
Every artifact ships a manifest.json validated against a JSON Schema at publish time. Required fields: name, version, format, supports.
| Field | Type | Description |
|---|---|---|
name |
string | The template’s identity, lowercase, hyphenated, stable across versions. |
version |
string | Semver. Stable versions are immutable once submitted. Owner-only development versions use x.y.z-dev.N, remain non-promotable, and may be replaced before creating a stable release. |
format |
port60-liquid@1 |
The dialect/contract major this template is written against. |
label |
string | Human-readable name shown in pickers. |
description |
string | |
requiresCapabilities |
array | Platform capabilities this design needs in order to work as intended. This is a catalogue matching signal, never an entitlement grant. The validator checks that each declared capability has a corresponding section, page template, island or context surface. |
suitsProfiles |
array | Organisation profiles this design was composed for. An empty list means the template is general purpose. Profiles improve catalogue recommendations but do not hide or unlock features. |
changelog |
string | One human line describing what THIS version changed, shown on the Appearance update nudge. |
supports |
object | |
supports.pages |
array | Section based page bodies this template renders from sections/{type}.liquid. Contract v1 supports home and about. This is independent of layout chrome and route-specific pageTemplates. |
supports.sections |
array | Section types this template ships renderers for (sections/{type}.liquid). Unsupported types are omitted at render, never an error. |
supports.islands |
array | Islands this template places. Every {% island %} name must be listed here AND exist in the platform island registry. |
supports.layout |
boolean | True when the template ships layout.liquid and owns the page chrome (header/nav/footer). The platform still owns , consent, and identity, see the layouts guide. |
supports.pageTemplates |
array | Route-specific data views rendered from pages/{page}.liquid with documented context. Events and articles own listings; course and article own detail presentation. Event transactions, course enrolment, article engagement, comments, identity and consent stay platform-owned islands. Requires supports.layout; without an entry the platform body renders inside the template chrome. |
supports.worship |
boolean | True when the layout renders the worship context (the prayer/service-times rail). Declared honesty: the choosers badge worship-enabled tenants toward looks that show their times, and warn on looks that don’t. Requires supports.layout; the layout must actually branch on worship. |
supports.heroImagery |
boolean | True when the homeHero renderer displays the tenant’s hero photographs (homeHero.images, legacy imageUrl), always under a TREATMENT built from the template’s own palette variables (scrim/tint/blend), never raw. Declared honesty, proven behaviourally at validation: the choosers badge photo-led tenants toward templates that show their images and warn on ones that won’t. Requires the homeHero section among supports.sections. |
supports.behaviors |
array | Engine-attached behaviours this template’s markup opts into via data-p60-* attributes (see the behaviour catalogue). Templates never ship JavaScript; declaring here is what makes the engine wire the markup, and the validator proves declaration and usage agree in both directions. |
supports.focus |
array | The site-focus kinds this template can LEAD with (docs/volunteering.md). Declaring ‘volunteer’ means the home hero places the primary_action_widget island (the widget that becomes the donation widget or the volunteer sign-up as the charity chooses) or branches on section.primary.kind, so the volunteer sign-up can take the widget slot; the picker marks templates that cannot lead with volunteering. Templates that predate the setting keep rendering the donation widget, since donate is the default. |
imagery |
object | The template’s OWN image advice, surfaced beside uploads in the charity’s editor, a design statement (what shape suits THIS composition), advisory only, never enforced. Entity images (events, courses, articles) are platform-scoped instead: see the contract’s imagery.json envelopes. |
imagery.hero |
object | |
imagery.hero.idealAspect |
string | The aspect this hero composes best with, e.g. “16:9”. |
imagery.hero.minWidth |
integer | Below this pixel width the photo may look soft full-bleed. |
imagery.hero.note |
string | One human line of advice, e.g. how panoramas or portraits behave in this hero. |
fonts |
array | Webfont stylesheet URLs the platform loads in (with preconnects, never a CSS @import). Google Fonts css2 URLs only. |
looks |
array | One-click LOOKS: author-named bundles of knob values (scheme + fonts + width…) applied together. Everything remains individually adjustable afterwards. |
looks[].name |
string | The look’s evocative, author-chosen name, shown as a one-click chip in Appearance. |
looks[].values |
object | Knob key → value. Every key must be a declared settings knob; select values must be listed options; font values must be catalogue families. |
compositions |
object | The template’s preferred composition per supported page (site editor stage 4): the ordered section types it designs the page around, each with a role. core sections carry the design (removing one warns, never locks), recommended ones are on by default, optional ones are offered. A page a tenant never edited renders this composition; the Pages editor offers it as the order to reset to. |
settings |
object | |
settings.schema |
array | The template’s own theming knobs (colour schemes etc.), rendered as an editor in Appearance/get-started; values land in site-config config.theme. |
settings.schema[].key |
string | |
settings.schema[].kind |
color | select | toggle | font |
|
settings.schema[].label |
string | |
settings.schema[].default |
||
settings.schema[].options |
array | |
settings.schema[].group |
typography | looks |
Where the admin surfaces this knob: ‘typography’ → the Typography section beside the font slots; ‘looks’ → the Looks section with the one-click bundles (colour schemes and accent dials belong there, and ungrouped color knobs plus a knob keyed ‘scheme’ default there anyway); other ungrouped knobs render under Template options. |
settings.schema[].weights |
array | FONT knobs only: the weights this template’s typographic system uses for the slot. The host requests these for the tenant’s chosen family (clamped to the family’s real weights). The tenant picks the FAMILY; weights, sizes and tracking stay template-owned. |
Worked example, the starter template’s manifest
Section titled “Worked example, the starter template’s manifest”{ "name": "starter", "version": "1.13.0", "format": "port60-liquid@1", "label": "Starter", "description": "The reference template for the Port60 dialect, the developer docs' worked example and the base to copy when building your own. 1.1.0 adds the layout slot: the template owns the header, navigation and footer chrome.", "supports": { "pages": [ "home", "about" ], "sections": [ "homeHero", "hero", "values", "cta", "people", "campaigns", "impactMap" ], "islands": [ "donation_widget", "member_menu", "hero_carousel", "map", "primary_action_widget", "search" ], "layout": true, "heroImagery": true, "behaviors": [ "reveal", "carousel" ], "focus": [ "donate", "volunteer" ] }, "compositions": { "home": [ { "type": "homeHero", "role": "core" }, { "type": "campaigns", "role": "recommended" }, { "type": "impactMap", "role": "optional" }, { "type": "cta", "role": "recommended" } ], "about": [ { "type": "hero", "role": "core" }, { "type": "values", "role": "recommended" }, { "type": "people", "role": "optional" }, { "type": "cta", "role": "recommended" } ] }, "settings": { "schema": [ { "key": "siteFont", "kind": "font", "label": "Site font", "default": "Inter", "weights": [ 400, 500, 600, 700, 800 ] } ] }, "changelog": "Places the site search box in the header: a typeahead across events, articles, services, campaigns, appeals, volunteering and documents, with the results page at /search.", "imagery": { "hero": { "idealAspect": "16:9", "minWidth": 1600, "note": "Landscape around 16:9 fills the hero cleanly; panoramas suit the whole-photo framing." } }}
