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