Content files
The files in content/ that the admin panel edits, including collections and Markdown items.
Everything the admin panel edits is a file in content/ or public/media/, in the site's repository. The format is part of Goodfellow's public interface: it's versioned, and changes come with migrations.
| File | What it holds |
|---|---|
content/site.json |
Site Settings: name, address, logo, icons, contact details, theme |
content/menus.json |
Navigation menus, by name |
content/redirects.json |
Redirects: old addresses, and where they send visitors |
content/media.json |
Credits for media files, such as photos the AI assistant found, by address |
content/media-storage.json |
Where a plugin keeps media: the plugin, the public address it keeps files at, and each file it keeps by its /media/ address, with its size |
content/permissions.json |
What editors with limits may change, by username, and who reviews the rest |
content/layout/header.json, footer.json |
The header and footer, as Puck data |
content/pages/about.json |
The page at /about, as Puck data; index.json is a folder's own page |
content/collections/<id>/_collection.json |
A collection's settings, fields and page design |
content/collections/<id>/<slug>.json or .md |
One item: its field values |
content/styles/custom.css |
The custom CSS from Site Settings |
content/styles/theme.json |
Which theme is installed, and what it installed, so an update keeps what owners changed |
public/media/ |
Uploaded images and files, served at /media/, apart from those a plugin keeps |
Every JSON file has a version, and is written with two-space indentation, keys in a fixed order and a trailing newline, so changes read well in a diff. If you edit one by hand, run the build, which reports any file that doesn't fit, with what's wrong.
Collections
A collection's _collection.json has its name, entryName (what one item is called), its fields, an optional path such as /events/{slug} for items' pages, an optional sort, and its template, the page design. Every collection has a text field named title. A field's name is what content refers to, so it never changes once items use it.
An item's JSON file holds { "version": 1, "fields": { … } }. A choice field (select) holds one of its options' values, and a tags field (tags) a list of them, such as ["music", "youth"].
Editors' limits
content/permissions.json names each person with limits by their sign-in name, which compares without case, and what they may change. People it doesn't name have no limits. reviewers is who's asked to review changes outside someone's limits; without it, every owner is.
{
"version": 1,
"people": {
"maria": { "pages": ["/news"], "collections": ["events"], "media": true, "languages": ["en"] }
},
"reviewers": ["owen"]
}pages: page sections by address, each with the pages under it and their translations;/is every page.collections: collections by their folder's name: their items, not their settings.media,layout,menus: the media library, the header and footer, and the menus.languages: only pages, items, headers, footers and menus in these languages. Without it, every language.
Pages' settings
A page's root.props hold its settings: title, description, image (for link previews), className and noIndex, which is true for a page hidden from search engines (see Search engines and feeds). A collection's template has the same, which its items' pages share.
Scheduled pages and items
A page's or item's file can say when it's on the site, beside its version: showFrom, when it's first shown, and hideAfter, when it's hidden again, each a wall-clock time YYYY-MM-DDTHH:MM in the site's timeZone (UTC without one). Either can be left out, and hideAfter must come after showFrom. A Markdown item has them in its front matter. Builds leave out pages and items their schedule doesn't show at the time they run (see Scheduled publishing).
{
"version": 1,
"data": { "root": { "props": { "title": "Advent" } }, "content": [] },
"showFrom": "2026-11-29T09:00"
}Translations
On a site in several languages, a page's file can say which page it translates, translationOf, with translatedFrom, a hash of that page as it was when it was translated, which tells whether the translation is out of date. An item's file has inLanguage too, since an item's language isn't its address. Both go beside version, and in Markdown items' front matter.
{
"version": 1,
"translationOf": "/about",
"translatedFrom": "3f1c9a0b5d7e2468",
"data": { "root": { "props": { "title": "Quiénes somos" } }, "content": [] }
}Pages blocks add
Some blocks give the page they're on pages of their own, which builds write beside it: a Calendar showing a month at a time a page for each month (/calendar/2026-11), and a Collection list or loop its later pages (/news/page/2) and a page for each choice of a field (/news/topics/music, from the choice's value). These pages aren't in content/: they're the same page, showing something else. They never take an address a page or item already has.
Events
A collection with "calendar": { "when": "when", "place": "place", "summary": "summary" } is a calendar: when names its event field (type event), and place and summary the fields calendars show beside each event. An event field's value is the site's own wall-clock time, in the time zone content/site.json names as timeZone (such as "America/New_York"):
{
"start": "2026-10-04T09:00",
"end": "2026-10-04T10:00",
"repeat": { "every": "week", "days": ["we", "su"], "skip": ["2026-12-27"], "until": "2027-06-27" }
}start and end are YYYY-MM-DD for an all-day event, whose end is its last day. repeat.every is day, week, month or year, with an optional interval; weekly repeats can name days, and monthly ones say on the start's date (the default), its weekday of the month (the second Tuesday) or the last such weekday. until and skip are dates.
Builds write each calendar collection as /calendars/<id>.ics, and each of its events as /calendars/<id>/<slug>.ics, and give a page whose Calendar block shows a month at a time a page for each month (see Pages blocks add).
Markdown items
A collection whose settings have "markdown": { "body": "body" } stores each item as <slug>.md: the body field is the Markdown text, and every other field is in the front matter, in YAML, beside the version.
---
version: 1
title: Writing blocks
section: developers
order: 2
---
A block is a Puck component…The Markdown is GitHub-flavored (tables, strikethrough, task lists). HTML in it is shown as text rather than run, and links and images can only use addresses that can't run code. Headings get ids for linking, as GitHub gives them: "Writing blocks" becomes #writing-blocks. Code blocks are highlighted when the site is built.