Collections API
Read, create, update, delete collections (admin-only).
All endpoints require an admin JWT.
GET /api/v1/collections → { "data": [ {Collection}, ... ] }Get one
Section titled “Get one”GET /api/v1/collections/<id_or_name> → { "data": {Collection} }Create
Section titled “Create”POST /api/v1/collections{ "name": "posts", "type": "base", // "base" | "auth" | "view" | "singleton" "fields": [ { "name": "title", "type": "text", "required": true }, { "name": "body", "type": "text" } ], "view_query": "...", // required for type=view "list_rule": null, "view_rule": null, // null = public, "" = admin only "create_rule": "", "update_rule": "", "delete_rule": "" // omitted = "" (admin only)}Returns the created collection. Errors:
400— duplicate name422— bad type, missingview_queryfor view, reserved field names (on every collection:id,created,updated,created_at,updated_at,collectionId,collectionName,expand; on auth also the auth columns), malformed view query, or a rule that doesn’t parse (the error names the rule)
If the table can’t be created, nothing is saved.
For auth collections, email + verified implicit fields are auto-injected
if you don’t supply them.
A singleton collection is created with its one record, every field empty. See
Singleton collections.
Rules you omit default to "" (admin only), for every collection type — pass null
explicitly to make an action public.
A view query must be a single SELECT. It can’t reference SQLite metadata
(sqlite_master, sqlite_schema or pragma_*() functions) — the query is rejected
with 422.
Update
Section titled “Update”PATCH /api/v1/collections/<id_or_name>{ "fields": [...], "view_query": "...", "list_rule": "...", // ...same shape as create, but partial — type can't change}A collection can’t be turned into a singleton or out of one. Create a new collection and move the data.
Re-runs ALTER TABLE on field-list changes. Type changes within an existing
field are blocked (422) — drop + re-add to change a type. Rules are checked
when saved: one that doesn’t parse (a collection rule, or a file field’s
viewRule) answers 422 naming the rule. An update checks only the rules it
sets. An unknown collection is a 404.
Unique fields: adding a field with "options": { "unique": true }, or switching unique
on for an existing field, creates a unique index (422 if existing records
already have duplicate values). Switching it off drops the index. A field that
was created as unique together with the collection keeps a built-in
constraint that can’t be removed in place (422); drop and re-add the field.
Renaming ("name": "new_name") renames the table (or rebuilds the view)
and its indexes, and updates relation fields in other collections, hooks and
record history to the new name. 400 if the name is taken or not a valid
identifier. Free-text references are not rewritten: update rules that use
@collection.old_name, webhook event patterns such as old_name.create, and
API token scopes yourself.
Delete
Section titled “Delete”DELETE /api/v1/collections/<id_or_name> → { "data": null }Drops the underlying table or view. Records are gone. No undo. An unknown
collection is a 404.
The Collection shape
Section titled “The Collection shape”interface Collection { id: string; name: string; type: "base" | "auth" | "view" | "singleton"; fields: string; // JSON-encoded array of FieldDef view_query: string | null; list_rule: string | null; view_rule: string | null; create_rule: string | null; update_rule: string | null; delete_rule: string | null; history_enabled: number; // 0/1 — see Record history created_at: number; // unix seconds updated_at: number;}Schema migrations
Section titled “Schema migrations”Use the snapshot endpoints to ship schemas across environments:
GET /api/v1/admin/migrations/snapshot → JSON file with every collection's full definition
POST /api/v1/admin/migrations/apply{ "snapshot": { ...JSON snapshot... }, "mode": "additive" | "sync" // default "additive"}additive creates missing collections; sync also updates existing ones.
Neither deletes anything. See Backups & migrations.
Renaming a field
Section titled “Renaming a field”Send the field under its new name with renamedFrom set to the old one; the column is
renamed in place and keeps its data, its unique index and its search index. A field that
arrives under a new name without the hint is treated as a new field: the old column is
dropped. The admin editor sends the hint for every field whose name you changed. A rename
can’t change the field’s type in the same step.
{ "fields": [{ "name": "headline", "type": "text", "renamedFrom": "title" }] }DB indexes
Section titled “DB indexes”Per-collection indexes are managed via:
GET /api/v1/admin/collections/<name>/indexesPOST /api/v1/admin/collections/<name>/indexes { fields: [...], unique?: bool }DELETE /api/v1/admin/collections/<name>/indexes/<index_name>An index covers one to four fields, in the order given: fields of the collection, or
id, created_at or updated_at. POST also accepts the older { field } for a
single field. The name is generated from the fields (idx_posts_status_created_at, or
uniq_…); GET lists each index with its fields. Editable from
collection → Indexes in the admin UI.
Put the fields a list filters on with = first, then the field it sorts by: a list
filtered on status with the default sort is served by (status, created_at), one
sorted by -views by (views). The Monitoring page records every list query’s
shape and, for lists that read a whole table or sort in memory, names the index that
would serve them and creates it in one click. See Indexes.