Publishing and access

Whether a page is live, listed, open to everyone, or protected from change: four separate settings.

Four settings

A page has four independent settings in its index.json. Each answers one question:

Setting Question Values
published Is it live? true (default) or false
access Who may open it? empty (anyone), login, or role names joined by |
hidden Is it listed? false (default) or true
locked Can it be changed? false (default) or true

None depends on another: an unpublished page can be hidden, a published page can be members-only, and a locked page can be any of them. A page with no published key is published. The admin writes "published": false on every new page, so nothing goes public until someone publishes it.

In the admin they are on the Settings tab of the edit panel. Saving them needs the pages.publish permission. The CLI commands are shown under each setting.

Published

An unpublished page is not live. Visitors get a 404 and it stays out of every listing. Editors (anyone who can open the admin) still see it, so you can preview before publishing.

phlat page:publish blog/first-post
phlat page:publish blog/first-post --off

Publishing applies to everything beneath the page: a published page inside an unpublished section can't be opened until the section is published.

Being published is separate from the edit panel's drafts. Unsaved edits are autosaved as a draft that only editors see; Save turns the draft into the page and adds it to the page's history.

Access

access limits who may open a published page:

Value Who may open it
empty Anyone
login Any signed-in user
editor Signed-in users with the editor role
editor|admin Users with either role

An anonymous visitor is sent to login and back again. A signed-in visitor with the wrong role gets a 403. A signed-in visitor holds login and their role, so naming either lets them in.

Each name must be login or an existing role (see Users and roles); anything else throws a ValidationException, so a typo can't lock everyone out. Like published, it applies to everything beneath the page.

phlat page:access members --to=login
phlat page:access staff --to='editor|admin'
phlat page:access members --to=public

access decides who may see a page. What a user may do is a permission.

Hidden

hidden keeps a page out of listings, not out of reach. It is left out of children(), siblings(), next() and prev(). find() still returns it unless you add hidden=0, and its URL still works.

Use it for pages linked to directly that don't belong in navigation, such as a landing or thank-you page. It is not protection; use access for that. A hidden page keeps its flag when published, so you can stage a page and publish it later without it ever appearing in navigation.

Locked

locked protects a page from change. While true, a save is rejected unless it only changes locked itself, and the page can't be moved or deleted. A parent of a locked page can't be deleted either. It doesn't affect who can see the page.

Who sees what

Visitor Unpublished Behind access Published and open
Anonymous no no, sent to login yes
Signed in, no admin access no if login or their role matches yes
Editor (admin.access) yes yes yes
CLI yes yes yes

Editors see everything because the admin preview is the real site. $app->isEditor() is that check, and the CLI counts as an editor.

Where it applies

One rule, applied in three places:

  • Requests. Page::route() checks the page and each ancestor first, so no router, controller or file request on a closed page ever runs.
  • Listings. find(), children() and everything built on them return only pages the visitor may open. The filter is part of the query, so limit= and counts stay correct.
  • References. pages and relation fields skip pages the visitor may not open.

$app->page($path) is not checked. It is a direct lookup and returns the page whether or not it is published. When the path comes from outside, check it:

$page = $app->page($path);
$page->assertViewable();   // 404, login redirect or 403 as appropriate

isViewable() is the same check as a bool.

In templates and queries

All four settings are typed properties on $page:

{if $page->access !== ''}<span class="badge">Members only</span>{/if}

Editors and the CLI can filter on them like any structural field. Everyone else only gets rows they may open, so the same queries return nothing for them.

$app->find('type=page, published=0, sort=-updated');
$app->find('type=page, access=login');

Limits

Image variants are written into site/public/ on first request and served directly by the web server from then on. A variant generated before its page was unpublished or got an access keeps being served. Original files, and variants not yet generated, go through the page and are checked.

A cached response is not revoked when a page changes. If a view opts into caching with cache, anonymous visitors can keep getting the old page until the cache expires.