Pages
Every page is a folder on disk. data.json holds content fields; index.json holds structural metadata.
Page structure
Pages live in site/pages/. The folder name is the URL slug. Nesting creates nested URLs:
site/pages/
data.json ← / (root page)
index.json
blog/
data.json ← /blog/
index.json
my-post/
data.json ← /blog/my-post/
index.json
data.json
Holds content fields. Any key is valid — the view template decides what to use.
{
"title": "My Post",
"body": "Hello world."
}
index.json
Holds structural metadata. Created automatically on the first request if missing.
{
"uuid": "abc123",
"view": "post",
"title": "My Post",
"status": "published",
"hidden": false,
"locked": false,
"created": "2026-01-01T00:00:00+00:00",
"updated": "2026-01-01T00:00:00+00:00"
}
hidden pages are excluded from children(). locked pages cannot be deleted via the admin. status is available for filtering but not enforced by the framework.
Template variables
The current page is always available as $page. Structural properties (title, view, uuid, status, hidden, created, updated) are typed readonly strings:
{$page->title}
{$page->url()}
{$page->created}
Content fields (everything else from data.json) are accessed via __get, which returns a Field object:
{$page->body} {* Field::__toString() — decoded string *}
{$page->body|render} {* rendered via the field's type template *}
{if !$page->body->empty()}
<div class="prose">{$page->body|render}</div>
{/if}
Fetching pages in PHP
$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() // ObjectCollection of direct children (hidden excluded)
$page->child('name') // single child by slug
$page->parent() // parent Page or null
$page->parents() // ObjectCollection of all ancestors, root first
children() reads order from the children key in index.json if present; otherwise scans the folder. The result is cached on the page instance.
Querying children
ObjectCollection::find() filters and sorts by a query string:
$recent = $page->children()->find('status=published, sort=-created');
$tagged = $page->children()->find('tag*=php'); // field contains value
$limited = $recent->limit(5);
Operators: = != *= (contains) ^= (starts) $= (ends) < > <= >=. Sort prefix - for descending.
Page files
Files placed in a page folder are accessible as File objects:
$files = $page->files();
$url = $files[0]->url();
$size = $files[0]->size;
<?php
// Fetch a page
$post = $app->page('blog/my-post');
echo $post->title; // typed property — plain string
echo $post->url(); // /blog/my-post/
// Access a content field
$field = $post->body; // Field object
$raw = $field->value; // raw stored string
$html = $field->decode(); // decoded value (HTML for markdown, etc.)
$empty = $field->empty(); // true if decoded value is blank
// Query children
$posts = $app->page('blog')
->children()
->find('status=published, sort=-created')
->limit(10);