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();