Schema changes
Included free on every install. The schema canvas and saving presets of your own need a license with the
schema-profeature, and moving configuration between instances needsconfig-sync. See pricing.
A content type changes while your site keeps serving it. You send the new definition, and LyEve changes only what differs, so existing entries keep their values. Before anything runs, you can see the exact database statements a change would make. A removal never runs by accident: the data stays until a super admin applies it.
What a content type holds, its fields, field types and relations, is explained on the data model page.
How it works
Section titled “How it works”Each save compares the new definition with the stored one and acts on the difference:
| You change | What happens |
|---|---|
| Add a field | Applied at once. Existing entries have no value for it. |
Turn required, unique or indexed on or off | Applied at once. |
| Change a field's type | Applied at once, and the stored values are converted. On PostgreSQL a value that does not convert, such as abc into a number, fails the save with 422 schema migration failed, and nothing changes. |
Remove a field, or turn off with_soft_delete or with_draft_publish | Queued. The data stays until a super admin applies it. |
| Rename a field or the content type | Use the rename routes below. A definition that just uses the new name is read as one field added and one removed. |
Try it
Section titled “Try it”This adds a field to a content type, removes another, and applies the removal. You need a
token in TOKEN for a super_admin. The quickstart
shows how to get one.
-
Create a
notecontent type with acolorfield:Terminal window curl -X POST http://localhost:3001/api/admin/schemas \-H "Authorization: Bearer $TOKEN" \-H "Content-Type: application/json" \-d '{"name": "note", "display_name": "Note", "fields": [{"name": "title", "field_type": "text", "required": true}, {"name": "color", "field_type": "text"}]}'The answer is
200with the stored definition, which now starts with theidfield every content type gets. -
Preview a change that adds
pinnedand dropscolor. Nothing is changed yet:Terminal window curl -X POST http://localhost:3001/api/admin/schemas/note/preview-ddl \-H "Authorization: Bearer $TOKEN" \-H "Content-Type: application/json" \-d '{"display_name": "Note", "fields": [{"name": "title", "field_type": "text", "required": true}, {"name": "pinned", "field_type": "boolean"}]}'{"statements": [{"description": "add column _note.pinned","sql": "ALTER TABLE \"_note\" ADD COLUMN IF NOT EXISTS \"pinned\" BOOLEAN","down_sql": "ALTER TABLE \"_note\" DROP COLUMN IF EXISTS \"pinned\""},{"description": "drop column _note.color","sql": "ALTER TABLE \"_note\" DROP COLUMN IF EXISTS \"color\"","down_sql": "-- no safe undo for drop of _note.color","destructive": true}]}"destructive": truemarks a statement the save queues instead of running. The SQL is written for your database, PostgreSQL here. -
Save the change by sending the same definition with its name:
Terminal window curl -X POST http://localhost:3001/api/admin/schemas \-H "Authorization: Bearer $TOKEN" \-H "Content-Type: application/json" \-d '{"name": "note", "display_name": "Note", "fields": [{"name": "title", "field_type": "text", "required": true}, {"name": "pinned", "field_type": "boolean"}]}'pinnedexists now.colorand its values are still stored. -
See what is queued:
Terminal window curl http://localhost:3001/api/admin/schemas/note/history \-H "Authorization: Bearer $TOKEN"The answer lists each statement with its
state(applied,pendingorfailed), and"pending": 1. -
Apply the removal:
Terminal window curl -X POST http://localhost:3001/api/admin/schemas/note/apply-pending \-H "Authorization: Bearer $TOKEN"{ "applied": 1, "canceled": 0 } -
In the admin console, open Schema builder and pick Note. Preview DDL shows the statements for unsaved edits, and History lists what ran and what is queued.
Preview a change
Section titled “Preview a change”Two routes answer the statements a definition would run, and change nothing:
POST /api/admin/schemas/{name}/preview-ddlmarks each queued statement with"destructive": true. The Schema builder reads this one.POST /api/admin/schemas/{name}/previewanswers the same statements without the mark.
Each statement has a description, its sql, and a down_sql that undoes it where that is
possible. The name in the path wins over a name in the body.
Save a change
Section titled “Save a change”POST /api/admin/schemas creates a content type or updates the one with the same name. It
takes admin or super_admin.
PUT /api/admin/schemas/{name} replaces the definition of a content type that exists and
answers 404 for one that does not. It takes super_admin. A definition it refuses answers
400 with the reason, where POST answers 422.
Both answer 200 with the stored definition, and every save is recorded in the
audit log.
Rename a content type or a field
Section titled “Rename a content type or a field”curl -X PUT http://localhost:3001/api/admin/schemas/note/fields/pinned/rename \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"new_name": "starred"}'{ "schema": "note", "old_field": "pinned", "new_field": "starred" }PUT /api/admin/schemas/{name}/rename with {"new_name": "memo"} renames the content type
and answers its stored definition. Its Content API path moves to the new name, and the
entries stored there move with it. A name another content type already uses answers 409.
Apply removals
Section titled “Apply removals”A removal waits in a queue until a super admin applies it, because it changes stored data for every tenant that defines a content type of that name. A queued removal whose field a definition uses again is canceled instead of run.
| To | Call |
|---|---|
| See one content type's statements and what is queued | GET /api/admin/schemas/{name}/history |
| Apply one content type's queue | POST /api/admin/schemas/{name}/apply-pending |
| See every queued statement, by content type | GET /api/admin/migrate |
| Apply every queue | POST /api/admin/migrate/apply, or POST /api/admin/schemas/migrate, which also answers a message |
In the console, a super admin applies a queue from History in the Schema builder.
Delete a content type
Section titled “Delete a content type”DELETE /api/admin/schemas/{name} removes the definition and your tenant's entries, and
answers 204. Add ?content=soft to keep the entries the admin console wrote (their title,
slug and body) marked deleted instead, so a content type you create again under the same name
can take them back. The rows the Content API serves go either way. Deleting a content type
that does not exist also answers 204.
Work on the schema canvas
Section titled “Work on the schema canvas”The Schema builder has two views, List and Canvas. The list editor is free on every
install and builds every field type and every relation. The canvas needs a license with the schema-pro feature. It draws each content type as a box
and each relation as a line between two boxes, so you see the whole model at once.
On the canvas you can:
- Edit a content type. Pick its box, and the same editor the list view shows opens beside the diagram.
- Add a field. Start it from the box it belongs to.
- Add a relation. Draw it from one box to the content type it points at.
- Arrange the diagram. Drag the boxes where you want them. The layout saves a moment after you stop, and every admin of the tenant opens the same diagram, after a reload too.
Without schema-pro, choosing Canvas shows what the canvas does and that it comes with
Schema Pro, and the list editor works as before. If a license lapses, the saved layout stays.
The canvas opens in the same arrangement once the license is back.
Over the Admin API, PUT /api/admin/schemas/canvas-layout replaces the saved layout, up to
256 KiB, and answers 402 without schema-pro:
{ "positions": { "article": { "x": 120, "y": 40 }, "author": { "x": 420, "y": 40 } } }GET on the same path reads the saved layout on any install. Its answer also carries
licensed, which says whether this install has schema-pro. schema-pro is in beta, so the
layout's fields can still change before it is marked stable.
Move configuration between instances
Section titled “Move configuration between instances”With a license that carries config-sync, a super admin exports this instance's content
types, flows, access rules, webhooks and a few settings as one bundle sealed with a
passphrase, compares it with another instance, dry runs it and applies it there. See
Promote configuration. Moving one content type at a time stays
free, as described in Keep the content model and flows in Git.
Limits
Section titled “Limits”PUT /api/admin/schemas/{name} and POST /api/admin/schemas/migrate share a budget of ten
calls per tenant in any five minutes. Past it the answer is 429 with a Retry-After
header:
{ "error": "schema mutation rate limit exceeded", "retry_after": 120 }When two replicas apply a change to the same content type at once, one answers 503 with
Retry-After: 1. Send the request again.
Settings
Section titled “Settings”Schema changes have no settings of their own. They run on every install. If you set
LYEVE_PLUGINS to choose which features start, include schema in it. See
licensing and tiers.
Routes
Section titled “Routes”GET /api/admin/schemas, GET /api/admin/schemas/{name} and its stats also accept an
admin token with the schemas:read grant. Every other route
takes a signed-in session.
Content type routes
| Method | Path | Role | Purpose |
|---|---|---|---|
GET | /api/admin/schemas | signed in | List content types. |
GET | /api/admin/schemas/{name} | signed in | One content type. |
GET | /api/admin/schemas/{name}/stats | signed in | Your tenant's entry count, as rows, and last_updated. |
POST | /api/admin/schemas | admin | Create or update a content type. |
PUT | /api/admin/schemas/{name} | super_admin | Replace a content type's definition. |
DELETE | /api/admin/schemas/{name} | admin | Delete a content type and its entries. ?content=soft keeps the entries. |
PUT | /api/admin/schemas/{name}/rename | admin | Rename a content type. Body: new_name. |
PUT | /api/admin/schemas/{name}/fields/{field}/rename | admin | Rename a field and keep its data. Body: new_name. |
POST | /api/admin/schemas/{name}/preview-ddl | admin | The statements a definition would run, with queued ones marked. |
POST | /api/admin/schemas/{name}/preview | admin | The same statements, unmarked. |
GET | /api/admin/schemas/{name}/history | super_admin | The statements recorded for a content type, newest first, and how many are pending. |
POST | /api/admin/schemas/{name}/apply-pending | super_admin | Apply one content type's queue. |
GET | /api/admin/migrate | super_admin | Every queued statement, by content type. |
POST | /api/admin/migrate/apply | super_admin | Apply every queue. |
POST | /api/admin/schemas/migrate | super_admin | Apply every queue and report the count. |
GET, PUT | /api/admin/schemas/canvas-layout | admin | Read or save the canvas layout. Saving needs schema-pro. |
POST | /api/admin/config-sync/export, .../diff, .../apply | super_admin | Export, compare or apply a configuration bundle. Needs config-sync. See Promote configuration. |
GET | /api/v1/schemas, /api/v1/schemas/{name} | signed in | Read content types from the Content API. |
Errors
Section titled “Errors”The two you meet most:
422with the reason, such as an invalid name or an unknownfield_type, whenPOST /api/admin/schemasrefuses a definition. Nothing changes.429schema mutation rate limit exceededpast ten changes in five minutes.
Every error these routes return
| Status | Message | Cause |
|---|---|---|
400 | invalid JSON | The body is not JSON. |
400 | The reason the definition was refused | PUT with an invalid field or option. |
402 | payment_required | Saving the canvas layout needs schema-pro, or a configuration bundle route needs config-sync. |
404 | schema not found or field not found in schema | Unknown name. |
409 | a schema with that name already exists | A rename onto a name in use. |
409 | another schema shares this table and defines it differently: ... | Another tenant's content type of the same name stores the field as another type, or with a name that differs only in case. |
413 | layout too large | The canvas layout is over 256 KiB. |
422 | invalid schema: ... | POST with an invalid name, field or option. |
422 | schema migration failed | The database refused the change, such as stored values that do not convert to a new field type. Nothing changes. |
422 | new_name is required or new_name must differ from current name | Bad rename body. |
422 | content must be hard or soft | Bad content on delete. |
429 | schema mutation rate limit exceeded | Over ten changes in five minutes. |
503 | another instance is applying DDL for this schema; retry in 1s | Another replica is changing the same content type. Retry. |
503 | A static message | The database could not be reached. |
Related
Section titled “Related”- Data model: fields, field types and relations.
- Schema presets: ready-made content types in one call.
- Content type portability: export, import and keep definitions in your repository.
- Your first content type: build one step by step.