table({ name, schema }) def object. The schema is a map of column name to an
f.* field, and the row type follows from it.
Define tables
idandcreated_atare added at the head of every schema unless you declare them.idType: "uuid"gives a uuid primary key instead of an int.f.tableRef(table)links to another table. The target must be registered on the same workspace, or the export fails and names it.{ array: true }turns any scalar into a list column."unique"in an index is shorthand for"btree|unique".
xano/index.ts with .registerTables([users, posts]).
Seed rows
Give a tableseed rows and they are written when you deploy, so a fresh Xano Engine or
ephemeral starts with data:
id and rows are numbered for you. For a larger
set, keep the rows in a file with seed: seedFile("./categories.seed.json", import.meta.url).
Seed data is fixture data: keep secrets out of it.
Read and write rows
Endpoints, functions, tasks and triggers reach tables through thes.db.* statements:
s.db.bulk.update replaces every column an item omits with its zero value. Use
s.db.bulk.patch to update only the keys each item carries.
Related rows: addons
To return each post with its author, attach anaddon to the read. Do not run a second query
and loop over the rows to stitch them together. An addon is a single read against one table,
bound to the parent row through its inputs:
addon option, and map the parent row’s columns into its inputs
with out():
.registerAddons([author]). s.db.get, add, edit and patch
take the same addon option. Passing the handle types the graft in the endpoint’s response, so
the typed frontend sees _author. An as that shadows a real column
fails the build, which is why the alias starts with _.
React to row changes: table triggers
AtableTrigger runs a stack when rows in its table are inserted, updated, deleted or
truncated. It fires on every write, whichever endpoint, function or task made it. Put the
same side effect in one endpoint’s stack and every other write path skips it.
A trigger has no input. Its stack is a callback that receives a handle t: t.new is the
row after the change, t.old the row before it, and t.action the operation.
.registerTriggers([onPostChange, onPublish]).
- Bind
tableto the handle.t.new("title")andt.old("title")are then typed to the posts row, and a misspelled column does not compile. - The absent side is
{}, notnull. An insert’st.oldand a delete’st.neware empty objects, so branch ont.actionas above rather than on a null check. searchruns before the stack. It is the sameexpr()surface as awhere, with columns addressed asNEW.<column>andOLD.<column>. It cannot be combined withtruncate.- Triggers return nothing. A table trigger is config only, with no
response.
Use the row type
InferRow gives you a table’s row type anywhere in TypeScript:
Changing a table later
npm run xano:deploy merges into the Xano Engine’s existing data, and prints what a schema
change will cost (dropped columns, retyped columns) before it writes. Table changes that reach a
shared workspace are not rolled back by a later release, so read
Schema changes before you drop or retype a column there.
Next
- Joins, addon cardinalities, aggregates, vector search and every column type: authoring reference.
- Every trigger type’s actions and handle: object kinds reference.
- Put these tables behind endpoints and functions: APIs, functions and middleware.
- Work on a schedule instead of on a write: Tasks.