Collections
Items that belong to a page, such as comments or registrations, kept in a database of their own.
On this page
What is a collection?
A collection holds data that belong to a page (comments on an article). The items are not in the page's _data.json. All the items of one field live in a SQLite database at site/data/collections/{field}.db.
Pages go through git (and History). A collection never will: site/data/collections/ is gitignored by default. Keep them with backup:create or db:backup, which save each database whole.
Declaring one
A collection is one of your site's fields with type: @phlat/collection, and the sub-fields each item has go under options.fields. The field's name is also the name of its database file, so it uses lowercase letters and digits with underscores.
{
"label": "Comments",
"type": "@phlat/collection",
"options": {
"fields": {
"name": [],
"comment": []
}
}
}
List the field under fields in a view's _index.json, like any other.
Reading
$page->comments is that page's view of the collection. Calling it takes the same query syntax as find(), and every query is limited to this page's items:
{var $comments = $page->comments('sort=-created,limit=10')}
{foreach $comments as $comment}
<p>{$comment->name}: {$comment->comment}</p>
{/foreach}
| Method | Returns | Description |
|---|---|---|
new(array $data = [], string $uuid = '') |
Item |
Add an item to this page |
item(string $uuid) |
?Item |
One of this page's items |
items(array $uuids) |
RecordCollection |
Several of its items in one query, in the order given |
has(string $uuid) |
bool |
Whether this page has that item |
count(string $query = '') |
int |
Items matching the query |
sum(string $field, string $query = '') |
float |
The sum of a number field |
An Item is a Record. $item->name reads a field, and uuid, created, updated and locked are properties.
Writing
Each create, save and delete is one transaction. save() returns the item as saved, as it does on a page.
$comment = $page->comments->new(['name' => $name, 'comment' => $text]);
$comment = $comment->save(['comment' => 'Edited']);
$comment->delete();
Nothing can rebuild a collection, so its database runs synchronous=FULL: a write is on disk before it returns, at about 1.5 ms. A script that writes many items wraps them in atomic(), one transaction that lands together or not at all, at about 0.1 ms each:
$app->collection('comments')->atomic(function () use ($app, $page, $rows) {
foreach ($rows as $row)
$app->collection('comments')->create($page->uuid, $row);
});
Who sees the items
An item is open exactly when its page is. A visitor sees no items under an unpublished or gated page, and a count over them is 0. Every item belongs to a page: create() throws for an empty parent. A field with scoped: false reads past that rule, for a count or sum that a visitor's own request needs on a page they can't open. In code, $app->collection('comments')->unscoped() does the same.
Item uuids are unique within a collection, not across the site, so a bare uuid is not a full reference. $app->collection('comments')->get($uuid) reads one from anywhere, and so does {collection('comments')->get($uuid)} in a template. $page->comments->item($uuid) only finds an item of that page.
Files
An item has no files and no URL of its own. A file field on an item holds a bare filename, a file of the item's page, or {page uuid}:{filename} for a file of any page, and the file is served like any other file of that page.
Maintenance
phlat collection:prune events/summer registrations --where="created<2025-10-18" --dry-run
phlat collection:reindex registrations
phlat db:backup
phlat db:vacuum
collection:prune deletes the items that match a query and skips locked ones. collection:reindex redoes the search tables after a field's options change. See CLI.