v0.40.0
Latest release3 November 2026
Sourced pages replace virtual pages and Page::sync(), and a page stores only the fields its view declares.
Added
PageSource: a page's view-bound class implementsrecord($name)to build its children from an API, a database or any other source.child(),App::resolve(), relation and pages fields and the admin find a child through it. See Sourced pages.Page::isSourced()says whether a page's parent is aPageSource. A sourced page is never in the registry.- The admin opens and saves a sourced page that has no folder yet. The first save makes its folder, and drafts start from then on.
Record::base()is the one layer read under a record's_data.json: an addon mount root's shipped data, or a sourced page's record.- The API reference documents interfaces.
Changed
- A page stores only what its view owns.
save()andautosave()throw aValidationExceptionfor a key that is neither a field the view declares nor a structural key. A save that setsviewis checked against that view. - A draft holds the view's fields, and the admin preview reads it over the saved content rather than in its place.
save()on a record that doesn't exist throwsNotFoundException.- The second argument of
App::page($uri, $index)is the page's_index.json, for a caller that already holds it. It no longer builds a page from content.
Removed
Page::sync()and_source.json. Write pages in bulk withnew()andsave()inside$app->history()->batch(). A_source.jsonis no longer read, so fold it into its_data.jsonbefore upgrading.Page::isVirtual(),Page::virtual(), and the"virtual"and"children": {"source": true}view keys. A source implementsPageSource.- Pages built from content with
$app->page($uri, $data).