Tables
Create and manage database tables from the CLI. Each table automatically gets REST API endpoints.
Create a table
based table create <name> <columns...>
# Example
based table create posts \
title:text:required \
content:text \
status:text:default=draft \
author_id:text:ref=users.idColumn syntax
Each column is defined as name:type[:modifier...] where modifiers are optional and can be chained.
| Modifier | Example | Description |
|---|---|---|
| required | title:text:required | Column cannot be null |
| unique | slug:text:unique | Values must be unique |
| default=value | status:text:default=draft | Default value when omitted |
| ref=table.column | author_id:text:ref=users.id | Foreign key reference |
Column types
| Type | Description |
|---|---|
| text | UTF-8 string |
| integer | 64-bit signed integer |
| real | 64-bit floating point |
| blob | Binary data |
Auto-added columns
Every table automatically includes id (a text primary key the server fills with a nanoid), created_at, and updated_at. You do not need to define these.
Do not declare an id column yourself — both the CLI and the API now reject it. The column grammar has no primary-key modifier, so based table create posts id:text exits with an error, and POST /admin/tables answers 400 VALIDATION_ERROR for any id column that is not declared primaryKey: true. For a custom id type — SQLite's rowid-aliased integer key, say — post to /admin/tables with { name: "id", type: "integer", primaryKey: true }. The server discovers such a key correctly.
Column modifiers survive the round trip: a DEFAULT and a UNIQUE declared at creation are re-discovered on every server start, so the REST API still applies the default and still answers 409 CONFLICT on a duplicate. A string DEFAULT may not contain a quote: it lands in the CREATE TABLE statement as a SQL literal, so status:text:default=O'Brien is rejected with 400 VALIDATION_ERROR.
Access policies
A new table starts as owner-scoped if it has a user_id (or userId) column, otherwise private. Under owner-scoped the REST API filters reads to the caller, forces ownership on writes, and rejects anon access.
based table create notes \
user_id:text:required \
title:text:required \
body:textChange a table's policy with based table access — it writes the project's config.json via PATCH /admin/tables/:name/access:
based table access <table> <owner-scoped|public-read|private>
# Example: a blog anyone can read and only the owner can write
based table access posts public-readSee REST API → Access policies for what each policy allows.
Add columns
based table add-column <table> <columns...>
# Example
based table add-column posts \
likes:integer:default=0 \
published_at:textDrop columns
based table drop-column <table> <columns...>
# Example
based table drop-column posts likes published_atDrop a table
based table drop <name>
# Example
based table drop postsThis permanently deletes the table and all its data. There is no undo.
List tables
based table listGenerate types
After creating or modifying tables, run based typegen to regenerate your TypeScript types. This keeps your SDK types in sync with the database schema.
based typegen
# writes based.d.ts — import it as "./based"
based typegen -o src/types/based.d.tsSee the SDK docs for details on using the generated types with useQuery and useMutation. Only the project owner can run typegen and the other based table commands — they all go through /admin/*.