Create from a database table
When the table already lives in your database, the fastest path is the CRUD Wizard. You point it at a schema and a table, tick the columns you want the screen to see, mark which ones identify a row, and Liberty writes the four queries (_get, _put, _post, _delete) for you — including the :NAME_ORIGINAL rebind on UPDATE so a dialog's Save knows which row to find.
This is the recommended path for any table that already exists. Skip it only when the table doesn't exist yet or when you want hand-written SQL — see Create a custom query.
Where it lives
The wizard is reached from Settings → Connectors:
- Pick a connector in the left list.
- Switch the right pane's mode bar to Tables.
- Click + Add table at the top-right.
- A choice modal opens. Pick Generate from DB.
The wizard then walks the connector's pool (GET /api/sql/<connector>/_schemas) and presents the schemas it found.
The wizard layout
Step 1 — Pick a schema
The schema picker shows up only when the pool has more than one. PostgreSQL with a single public and SQLite skip it; Oracle (many schemas) requires it.
The wizard fetches schemas lazily — the initial open just lists names, the per-schema table walk happens after you pick. On a populous Oracle pool this saves you the 10+ seconds a full multi-schema walk would take.
Step 2 — Filter table names (optional)
A SQL LIKE pattern (customers%, F00%) typed in the Name filter box narrows the table list server-side, before the per-table column introspection runs. For pools with thousands of tables this is the difference between a snappy picker and a sluggish one.
The filter is debounced — type freely; the fetch fires ~350 ms after you stop.
Step 3 — Pick the table
Once you pick a table, the wizard pulls its columns and pre-fills two sets:
| Set | Pre-fill rule |
|---|---|
| Include | Every column is ticked. |
| Key | Every column the database marks as NOT NULL is ticked. |
These are guesses — uncheck the columns you don't want surfaced (BLOBs, audit columns, computed fields) and confirm the keys.
Step 4 — Tick columns and keys
Each row in the Columns list carries:
| Element | What it does |
|---|---|
| Include checkbox | When unticked, the column is dropped from _get's SELECT and from _post's INSERT. The dialog will not see it. |
| Column name + type | Read-only — comes from the live database. |
| KEY toggle | Pinned to the right. When ticked, this column drives the _put's WHERE clause (with :NAME_ORIGINAL) and the _delete's WHERE clause. |
A row with at least one key is required for UPDATE / DELETE to emit anything. The wizard refuses to save without one.
Step 5 — Pick which queries to emit
Four checkboxes — SELECT (_get), UPDATE (_put), INSERT (_post), DELETE (_delete). Tick the ones the table needs.
| Pattern | Tick |
|---|---|
| Read-only reference table. | SELECT only. |
| Standard CRUD table. | All four. |
| Append-only (audit log). | SELECT + INSERT. |
| Reference table edited only via INSERT-then-purge. | SELECT + INSERT + DELETE. |
The SELECT query is always emitted — at the very least the screen needs to read the table.
Step 6 — Read the preview, override if needed
The right column shows the generated SQL for every ticked CRUD slot, live. Edit any preview directly — your edit wins over the auto-gen until you change the column / key selection again, which restores the generated version.
The four shapes
-- _get : straight SELECT over the included columns
SELECT col1, col2, …
FROM <schema>.<table>
-- _post : INSERT bound to :col placeholders
INSERT INTO <schema>.<table> (col1, col2, …)
VALUES (:col1, :col2, …)
-- _put : UPDATE non-key columns, WHERE on key with :NAME_ORIGINAL
UPDATE <schema>.<table>
SET non_key1 = :non_key1, non_key2 = :non_key2, …
WHERE key1 = :key1_ORIGINAL
AND key2 = :key2_ORIGINAL
-- _delete : DELETE keyed on :NAME (the dialog binds the row's current values)
DELETE FROM <schema>.<table>
WHERE key1 = :key1
AND key2 = :key2
Why :NAME_ORIGINAL on UPDATE?
A dialog's Save sends two snapshots of every key column: the current value (:key1) and the value the row had when the dialog opened (:key1_ORIGINAL). The UPDATE matches on the original so the row is found even when the operator edited the key itself. The CRUD Wizard wires this convention for you — if you hand-write a _put later, mirror it.
Step 7 — Save
The Save button:
- Appends the ticked queries to the connector's queries list.
- Saves the connector file.
- Triggers a hot reload — the new queries are callable immediately.
The four new queries appear in the Tables tab grouped under their shared base name, with the four CRUD slot badges showing which were emitted.
Validation — what blocks Save
| Error | Cause | Fix |
|---|---|---|
| Pick a table. | No table selected. | Pick one. |
| Name the table. | The base name field is empty (auto-derived from the table name; clear it to override). | Type a base name — letters, digits, underscores. |
| Include at least one column. | Every column was unticked. | Tick at least one. |
| The read (_get) query is required. | SELECT was unticked. | Tick it back. |
| Pick at least one key column for UPDATE / DELETE. | UPDATE or DELETE is ticked but no column has KEY on. | Mark the key column(s). |
| A query named "X" already exists. | The base name produces a <base>_get (or _put / _post / _delete) that collides with an existing query on this connector. | Pick a different base, or delete the old one first. |
What you've just built
For a customers table with customer_id as the key, the wizard generated four queries:
| Name | Type | Used by |
|---|---|---|
customers_get | table (read) | The grid + form's read. |
customers_put | table (write) | The dialog's Save (UPDATE). |
customers_post | table (write) | The dialog's Save (INSERT). |
customers_delete | table (write) | The grid's Delete action. |
They land in the Tables tab as one row labelled customers with the four GET / PUT / POS / DEL badges all green. The next step is to build a screen on top — covered in the upcoming Screens section.
When to use the wizard vs hand-write
| Use the wizard | Hand-write (Add query on Unclassified) |
|---|---|
| The table exists in the DB. | The query is a custom report, an aggregate, or a stored procedure call. |
| You want the four standard CRUD shapes. | You need a JOIN, a GROUP BY, a RECURSIVE CTE. |
| You're starting from scratch and the columns are the source of truth. | You want a query that doesn't map 1-1 to a table. |
The wizard is the right tool for 80% of operational tables. The other 20% — analytics, reports, multi-table joins — go through Create a custom query.
What's next
- Create a custom query — when the wizard doesn't fit.
- Clone a query or a connector — start from an existing query instead of from scratch.
- Parameter binding — give the
:placeholderparams labels, defaults and bind values from screens.