Custom Tables
The hutly tables command group manages custom tables in your Hutly organization — both the table schemas (defined in YAML and created from disk) and the rows stored inside them.
Table schemas are editable in place. hutly tables push diffs your YAML against the live table and applies the difference — add or remove a column, toggle required, change a default, or add a foreign key or unique constraint, and the next push carries it out. Changing a column’s type (or its Data Dictionary link) is the one change the diff can’t express: that still requires --force-recreate, which drops the table and recreates it from the YAML, destroying every row. Rows, by contrast, are fully mutable: create, update, delete, and bulk-import them with hutly tables rows.
Every table is created with a server-assigned, auto-increment id column. Never declare your own primary-key column — id, record_id, row_id, pk, primary_key, and <table_name>_id are all rejected. A _id suffix is for a foreign key pointing at another table (name it <parent_table>_id), not a substitute primary key.
All commands operate against the organization configured in your CLI context and require valid credentials. Every command accepts --json; JSON is the default machine-readable output for read commands, and the flag is a no-op elsewhere for scripting compatibility.
Schema commands
hutly tables list
List the custom tables in the current organization.
hutly tables list
hutly tables list --limit 50 --offset 50 --pretty| Option | Description |
|---|---|
-l, --limit <n> |
Max results (default: 20) |
-o, --offset <n> |
Result offset (default: 0) |
--pretty |
Pretty-print JSON output |
hutly tables get
Fetch a table’s schema plus its first page of rows (up to 100) as a single bundle.
hutly tables get applicants
hutly tables get applicants --pretty| Option | Description |
|---|---|
--pretty |
Pretty-print JSON output |
hutly tables describe
Fetch a table’s schema only, with no rows. Use get when you also want the first page of rows.
hutly tables describe applicants --pretty| Option | Description |
|---|---|
--pretty |
Pretty-print JSON output |
hutly tables push
Create or update a custom table from a YAML file. Table names must contain only letters, numbers, and underscores.
push is declarative: if the table doesn’t exist yet, it’s created. If it does, the CLI asks the server for a change plan — the list of steps needed to make the live table match the YAML — and applies it. Steps that destroy data (dropping a column drops its values) are flagged; the CLI prompts for confirmation before applying any of them, and --dry-run prints the plan without applying it at all.
Pass --force-recreate to drop the existing table — this deletes all of its rows — and recreate it from the YAML instead of diffing. This is still the only way to change a column’s type or its Data Dictionary link, since the diff can’t express either.
hutly tables push tables/applicants.yaml
hutly tables push tables/applicants.yaml --dry-run
hutly tables push tables/applicants.yaml --force-recreate
hutly tables push tables/applicants.yaml --force-recreate --yes
hutly tables push tables/applicants.yaml --var region=vic| Option | Description |
|---|---|
--dry-run |
Print the change plan without applying it |
--force-recreate |
If the table already exists, delete it (and all rows) and recreate from this YAML |
-v, --var <key=value> |
Set a variable value (repeatable) for YAML preprocessing |
-y, --yes |
Skip confirmation prompts |
Table schema YAML
A table YAML document has a name, an optional description, and a non-empty fields array. Each field is either a plain column or a Data Dictionary–linked column.
name: applicants
description: Rental applicants captured during screening
fields:
- name: full_name
type: text
required: true
comments: Applicant's full legal name
- name: monthly_income
type: numeric
required: false
comments: Stated gross monthly income
- name: screened
type: boolean
required: false
default: false
comments: Whether the applicant has been screenedPlain-column fields:
| Key | Description |
|---|---|
name |
Column name (letters, numbers, underscores) |
type |
One of text, numeric, boolean, timestamp, jsonb, reference |
required |
Whether the column is mandatory |
default |
Optional default value |
comments |
Optional column description |
fk |
(type: reference only) name of the custom table this column points at |
display |
(with fk only) name of a column on the parent table to show instead of the raw id — pick a text column (see below) |
Foreign keys point a reference column at another custom table’s id, in the same organization, ON DELETE RESTRICT — deleting a parent row a child still references is refused. display names the parent column shown in the UI instead of the raw id; a display without an fk is an error. display must be a text column: the row picker searches it with a case-insensitive text match, so a numeric or timestamp display column can’t be searched. This is enforced server-side — a schema update naming a display column that doesn’t exist on the parent table, or that exists but isn’t text, is rejected with a 400 naming the column and the table.
name: leases
fields:
- name: property_id # FK -> properties.id
type: reference
fk: properties
display: address_line_1
required: true
- ddField: rent_amount
required: true
unique:
- [property_id, rent_amount]Unique constraints live in a top-level unique array, each entry a list of one or more column names. Composite constraints are the common case once foreign keys exist — the example above means one rent value per property, and unique: [[property_id, inspection_date]] would mean one inspection per property per date.
Data Dictionary columns use the ddField shorthand, linking a column to a live Hutly Data Dictionary field by its canonical forms_fields.name. The server resolves and freezes the DD snapshot (label, type, enum values) at create time, so later DD edits don’t silently break the table.
name: lease_parties
fields:
- ddField: tenant_full_name
required: true
- ddField: lease_start_date
required: false| Key | Description |
|---|---|
ddField |
Canonical Data Dictionary field name to link |
required |
Whether the column is mandatory |
default |
Optional default value |
comments |
Optional column description |
hutly tables pull
Fetch a table’s schema and write it to a YAML file. Rows are not included — manage those with hutly tables rows. DD-linked columns round-trip as the ddField shorthand so a pulled file can be pushed back without invalidating the link.
hutly tables pull applicants
hutly tables pull applicants --output ./schemas/applicants.yaml| Option | Description |
|---|---|
-o, --output <file> |
Output file path (defaults to ./tables/<tableName>.yaml) |
hutly tables delete
Permanently delete a custom table and all of its rows. This is irreversible. The CLI prompts for confirmation; pass --yes to skip it.
hutly tables delete applicants
hutly tables delete applicants --yes| Option | Description |
|---|---|
-y, --yes |
Skip confirmation prompts |
Row commands
Rows live under hutly tables rows <verb> <tableName>.
hutly tables rows list
List rows in a table. Pass --filter and --order multiple times to build compound queries.
hutly tables rows list applicants
hutly tables rows list applicants --limit 50 --offset 0 --pretty
hutly tables rows list applicants \
--filter "monthly_income:gte:5000:and" \
--filter "screened:eq:true:and" \
--order "monthly_income:desc"| Option | Description |
|---|---|
-l, --limit <n> |
Max results (default: 100) |
-o, --offset <n> |
Result offset (default: 0) |
-f, --filter <segment> |
Filter field:op:value:join — repeatable |
--order <segment> |
Order field:asc or field:desc — repeatable |
--pretty |
Pretty-print JSON output |
Filter segments take the form field:op:value:join:
opis one ofeq,ne,gt,lt,gte,lte,like,ilike,in,ninjoinisandoror, combining the segment with the next one
Order segments take the form field:asc or field:desc; repeat for multi-key ordering.
hutly tables rows create
Insert a single row. Provide the column values as a JSON object that matches the table’s field schema, either inline or from a file.
hutly tables rows create applicants --data '{"full_name":"Sam Lee","monthly_income":6200,"screened":false}'
hutly tables rows create applicants --data-file ./row.json| Option | Description |
|---|---|
--data <json> |
Inline JSON object matching the table’s field schema |
--data-file <path> |
Read row JSON from a file |
Pass exactly one of --data or --data-file.
hutly tables rows update
Replace the columns of a single row, identified by its numeric id (a positive integer).
hutly tables rows update applicants 42 --data '{"screened":true}'
hutly tables rows update applicants 42 --data-file ./update.json| Option | Description |
|---|---|
--data <json> |
Inline JSON object with the new column values |
--data-file <path> |
Read row JSON from a file |
Pass exactly one of --data or --data-file.
hutly tables rows delete
Delete a single row by its numeric id. The CLI prompts for confirmation; pass --yes to skip it.
hutly tables rows delete applicants 42
hutly tables rows delete applicants 42 --yes| Option | Description |
|---|---|
-y, --yes |
Skip confirmation prompts |
hutly tables rows import
Bulk-insert rows from a JSON file. The file must contain a JSON array of row objects. The server validates each row independently and reports per-row errors; partial success is allowed. When any rows fail, the command prints the result envelope and exits non-zero.
hutly tables rows import applicants ./applicants-seed.json[
{ "full_name": "Sam Lee", "monthly_income": 6200, "screened": false },
{ "full_name": "Jo Park", "monthly_income": 4800, "screened": true }
]