Skip to content

Content model

Everything a template reads lives on one tree: site.brand, site.nav, site.socials, site.locale and the typed collections under site.content.*. Model version 1.0.

Four rules the model never breaks, so neither should your template:

  1. Bounded collections. Every list documents its cap and carries a moreHref, link onward, never assume you have everything.
  2. Open enums. Type fields grow; branch on the values you style and fall back for the rest (the validator proves your template survives unknown values).
  3. Explicit nullability. Optional fields are documented nullable; always branch.
  4. Add-only. Nothing you read today is removed within the major; deprecated means it stops being documented, never that it stops being served.

Your template’s content footprint (which paths it reads) and its minimum model version are computed at publish from your sources, you never declare either. Dynamic indexing into site.content is refused so that stays decidable. During development, preview-content.json beside your manifest replaces collection DATA wholesale (shape is schema-checked; the file is hot-reloaded, never packaged, refused at upload); the kit’s dev server renders this same reference with live example data at /model.

The organisation’s published content pages (the services CMS), newest curation order first.

Bounded at 24 · more at /services · since 1.0

Field Type Notes Since
id string 1.0
title string max 200 chars 1.0
slug string 1.0
summary string nullable, always branch · max 500 chars 1.0
imageUrl string nullable, always branch 1.0
href string 1.0

Example (the canonical fixture):

{
"id": "66666666-6666-6666-6666-666666666661",
"title": "Food Bank",
"slug": "food-bank",
"summary": "Emergency parcels, no referral needed, from the Foundry Lane depot every weekday morning.",
"imageUrl": "p60fixture:service/foodbank",
"href": "/services?service=food-bank"
}

Published events, soonest first. registrationMode is an OPEN enum: INFO, RSVP and TICKETED exist today and new modes will arrive; branch on the ones you style and fall back for the rest.

Bounded at 12 · more at /events · since 1.0

Field Type Notes Since
id string 1.0
name string max 200 chars 1.0
description string nullable, always branch · max 200 chars 1.0
registrationMode string open enum: INFO, RSVP, TICKETED today 1.0
startsAt string 1.0
endsAt string nullable, always branch 1.0
venueName string nullable, always branch · max 200 chars 1.0
online boolean 1.0
imageUrl string nullable, always branch 1.0
onSale boolean 1.0
soldOut boolean 1.0
priceFrom number nullable, always branch 1.0
currency string nullable, always branch 1.0
detailHref string 1.0

Example (the canonical fixture):

{
"id": "22222222-2222-2222-2222-222222222222",
"name": "The Lantern Gala",
"description": "Our biggest night of the year: supper, music from the Alderford Community Choir, and the annual lantern walk along the river to close.",
"registrationMode": "TICKETED",
"startsAt": "2030-09-20T19:00:00Z",
"endsAt": null,
"venueName": "The Maltings, Alderford",
"online": false,
"imageUrl": "p60fixture:event/gala",
"onSale": true,
"soldOut": false,
"priceFrom": 25,
"currency": "GBP",
"detailHref": "/events?event=22222222-2222-2222-2222-222222222222"
}

Published articles, newest first. Detail bodies stay editor-owned sanitised richtext on the article page BY DESIGN; the card model carries everything a listing needs.

Bounded at 12 · more at /articles/all · since 1.0

Field Type Notes Since
id string 1.0
title string max 200 chars 1.0
slug string 1.0
excerpt string nullable, always branch · max 500 chars 1.0
imageUrl string nullable, always branch 1.0
authorName string nullable, always branch · max 120 chars 1.0
authorRole string nullable, always branch · max 120 chars 1.0
categories string[] 1.0
publishedAt string 1.0
readingMinutes number nullable, always branch 1.0
href string 1.0

Example (the canonical fixture):

{
"id": "88888888-8888-8888-8888-888888888888",
"title": "The Community Garden Opens Its Gates",
"slug": "community-garden-opens",
"excerpt": "Two years of digging, fundraising and Saturday mornings in the rain: the Foundry Lane garden is finally open to everyone.",
"imageUrl": "p60fixture:article/garden",
"authorName": "Amina Khan",
"authorRole": "Community Editor",
"categories": [
"News"
],
"publishedAt": "2030-04-12T09:00:00Z",
"readingMinutes": 5,
"href": "/articles/community-garden-opens"
}

Live campaigns, curation order.

Bounded at 12 · more at /campaigns · since 1.0

Field Type Notes Since
id string 1.0
title string max 200 chars 1.0
href string 1.0
summary string nullable, always branch · max 500 chars 1.0
imageUrl string nullable, always branch 1.0

Example (the canonical fixture):

{
"id": "99999999-9999-9999-9999-999999999991",
"title": "Keep the 27 Bus",
"href": "/campaigns/keep-the-27-bus",
"summary": "The only bus serving the Fenwick estate is set to be cut. Add your name before the consultation closes.",
"imageUrl": "p60fixture:campaign/bus"
}

Causes currently accepting donations, quick-give first. paymentOptions mirrors the public cause contract.

Bounded at 12 · more at /donate · since 1.0

Field Type Notes Since
id string 1.0
title string max 200 chars 1.0
slug string 1.0
description string nullable, always branch · max 500 chars 1.0
imageUrl string nullable, always branch 1.0
targetAmount number nullable, always branch 1.0
raisedAmount number nullable, always branch 1.0
progressPercentage number nullable, always branch 1.0
href string 1.0
paymentOptions object nullable, always branch 1.0

Example (the canonical fixture):

{
"id": "55555555-5555-5555-5555-555555555550",
"title": "Flood Response Appeal",
"slug": "flood-response",
"description": "The Aldern broke its banks on Sunday. Thirty households need dry bedding, dehumidifiers and somewhere warm now.",
"imageUrl": "p60fixture:cause/flood",
"targetAmount": 50000,
"raisedAmount": 18750,
"progressPercentage": 38,
"href": "/donate/flood-response",
"paymentOptions": {
"oneOff": true,
"recurringCard": true,
"directDebit": true
}
}

Enrollable courses. The long-form About body is sanitised richtext on the course page, never in cards.

Bounded at 12 · more at /courses · since 1.0

Field Type Notes Since
id string 1.0
title string max 200 chars 1.0
description string nullable, always branch · max 500 chars 1.0
imageUrl string nullable, always branch 1.0
venue string nullable, always branch · max 200 chars 1.0
audience string open enum: ADULT, CHILD, FAMILY today 1.0
priceFrom number nullable, always branch 1.0
currency string nullable, always branch 1.0
soldOut boolean 1.0
href string 1.0

Example (the canonical fixture):

{
"id": "33333333-3333-3333-3333-333333333333",
"title": "Junior Athletics",
"description": "Weekly athletics for children aged 7 to 12, coached by qualified volunteers at the King's Park track.",
"imageUrl": "p60fixture:course/athletics",
"venue": "King's Park track",
"audience": "CHILD",
"priceFrom": 15,
"currency": "GBP",
"soldOut": false,
"href": "/courses"
}

Published documents, grouped by folder.

Bounded at 24 · more at /resources · since 1.0

Field Type Notes Since
id string 1.0
title string max 200 chars 1.0
folder string nullable, always branch 1.0
url string 1.0
contentType string nullable, always branch 1.0
bytes number nullable, always branch 1.0
publishedAt string nullable, always branch 1.0

Example (the canonical fixture):

{
"id": "88888888-8888-8888-8888-888888888881",
"title": "Annual report 2029.pdf",
"folder": "Reports",
"url": "/fixture/annual-report-2029.pdf",
"contentType": "application/pdf",
"bytes": 1245760,
"publishedAt": "2030-02-01T10:00:00Z"
}

The organisation’s physical places, primary first.

Bounded at 12 · more at /locations · since 1.0

Field Type Notes Since
id string 1.0
name string max 200 chars 1.0
address string nullable, always branch · max 500 chars 1.0
latitude number nullable, always branch 1.0
longitude number nullable, always branch 1.0
primary boolean 1.0
directionsHref string nullable, always branch 1.0

Example (the canonical fixture):

{
"id": "99999999-9999-9999-9999-999999999901",
"name": "Foundry Lane Hub",
"address": "14 Foundry Lane, Alderford AL2 4RS",
"latitude": 53.8008,
"longitude": -1.5491,
"primary": true,
"directionsHref": "https://www.google.com/maps/dir/?api=1&destination=Foundry%20Lane%20Hub"
}

TYPE-DERIVED recurring schedules. type is an OPEN enum; ‘worship’ is the first (its shape is the layout worship contract: times, labels, today, next). Other institutions arrive as new types; render the types you know, ignore the rest.

Bounded at 4 · since 1.0

Field Type Notes Since
type string open enum: worship today 1.0
content object 1.0

Example (the canonical fixture):

{
"type": "worship",
"content": {
"times": [
{
"name": "Fajr",
"begins": "03:20",
"congregation": "04:00"
},
{
"name": "Zuhr",
"begins": "13:11",
"congregation": "13:30"
},
{
"name": "Asr",
"begins": "18:30",
"congregation": "19:00"
},
{
"name": "Maghrib",
"begins": "21:04",
"congregation": "21:04"
},
{
"name": "Isha",
"begins": "22:12",
"congregation": "22:30"
}
],
"beginsLabel": "Begins",
"congregationLabel": "Jama'at",
"note": "Jum'a 13:15 & 14:15",
"href": "/services?service=prayer-times",
"today": "24 July 2026",
"todayAlt": "11 Safar 1448",
"next": {
"name": "Asr",
"begins": "18:30",
"congregation": "19:00",
"target": "2030-07-24T19:00:00+01:00"
},
"jumah": [
{
"name": "Jum'a 1",
"begins": null,
"congregation": "13:15"
},
{
"name": "Jum'a 2",
"begins": null,
"congregation": "14:15"
}
],
"hijri": {
"display": "11 Safar 1448"
},
"observances": [
{
"name": "Community iftar",
"date": "2 March 2030",
"href": "/events"
}
]
}
}

The admin-authored page composition: the ordered sections of the home/about surface. type is the section catalogue’s OPEN enum; content is that section type’s fields. This is what section.* re-addresses; a mega-page template can walk it directly.

Bounded at 24 · since 1.0

Field Type Notes Since
type string open enum 1.0
content object 1.0