Skip to content

Creating tables

The add-table builder is reached at /admin/tables/new. It is a single form that stages a draft table — profile, name, and an ordered list of columns — and creates it in one call. The server stays the single owner of the draft’s rules, the same ones the TUI client applies between keystrokes.

Method Route Purpose
GET /admin/tables/new renders the builder
POST /admin/tables/builder stages every button press and field change
POST /admin/tables creates the table

The builder can be entered with ?profile=<name>&global=true so the picker opens on the profile (or global scope) the admin panel handed over.

The profile selector lists existing profiles plus a special New profile choice (__new__). Choosing it reveals a text input for the new profile’s name; creating the table then creates the profile too. A new profile also asks for an accounting currency, because provisioning its accounting system tables requires one. Books are currently EUR-only, so a non-EUR currency is refused by the backend.

The global toggle puts the table in the Global scope (__global), shared by every profile. The table name must be an identifier, must not duplicate an existing table in the profile, and reserved names such as pg_catalog are refused.

The column panel is the same vocabulary described in How table definitions work. For each pending column you set:

  • name;
  • type (with the follow-ups for temporal, gtin, decimal(p,s), and link(table));
  • indexed toggle;
  • quantity ledger toggle (only for the types that allow it);
  • currency and rounding (exact or half-up) for money and accounting types;
  • row display toggle, which decides whether the column identifies a row.

Columns can be added, removed, reordered, and index-toggled before creation. The declared order is the order the table gets its columns in, and the preview shows what will really exist — including the system columns id, deleted, and created_at, and any columns a compound type expands into.

Choosing the accounting type (a compound, creation-only type) generates the managed columns name, tax_point_date, debit, credit, and account. The definition row must be named accounting and carry a currency equal to the profile’s book currency; the generated debit and credit columns inherit that currency and rounding.

Generated columns can be renamed from the builder. The alias fields have no change trigger of their own — type the names, then press the button that applies them, so the preview matches what the table will really be called. An alias must be a valid column name and must not collide with a declared column, another generated column, or a system column.

The draft is validated locally with the same checks the client runs, then the server re-validates authoritatively. A refused create answers 422 with the reason rendered into the builder. On success the browser redirects to the admin panel with the new table selected:

/admin?profile=<name>&table=<name>

From there the new table’s initial grants, columns, and other actions are one click away.