Pages
Every page is a folder on disk. data.json holds content fields; index.json holds structural metadata.
Structure
Pages live in data/pages/. The folder name is the URL slug, and nesting nests the URL:
data/pages/
data.json ← / (root page)
index.json
blog/
data.json ← /blog/
index.json
my-post/
data.json ← /blog/my-post/
index.json
data.json
Content fields. Any key is valid; the view decides which to use.
{
"title": "My Post",
"body": "Hello world."
}
index.json
Structural metadata. Every page folder needs one with at least a uuid, which is what marks the folder as a page. The admin and Page::new() create it for you.
{
"uuid": "abc123",
"view": "post",
"published": true,
"access": "",
"hidden": false,
"locked": false,
"created": "2026-01-01T00:00:00+00:00",
"updated": "2026-01-01T00:00:00+00:00"
}
published (on unless set to false) and access decide who can open the page, hidden keeps it out of listings, and locked blocks changes. They are independent. See Publishing and access.
In templates
$page is the current page. The structural keys (uuid, view, published, access, hidden, locked, created, updated) are typed readonly properties. Everything else, title included, is a content field from data.json that returns a FieldValue:
{$page->url()}
{$page->title} decoded string
{$page->body|render} via the field type's render template
{if !$page->body->empty()}
<div class="prose">{$page->body|render}</div>
{/if}
Fetching pages
$post = $app->page('blog/my-post'); // any page by path
$home = $app->page('/'); // root page
$active = $app->page(); // current request page
Children and parents
$page->children() // direct children; hidden pages and any the visitor may not open are excluded
$page->child('name') // named child, doesn't check it exists
$page->parent() // parent Page or null
$page->parents() // all ancestors, root first
children() reads the registry, not the filesystem. It is ordered by the view's sort key in its index.json unless the query overrides it.
children() also takes a registry query to filter and sort. PageCollection has no find(), so the query goes in the call:
$recent = $page->children('published=1, sort=-created');
$tagged = $page->children('tag*=php');
$limited = $recent->limit(5);
Files
Files in a page folder are PageFile objects. See Files.
$files = $page->files();
$url = $files[0]->url();