How table definitions work
A table definition describes the shape and meaning of one kind of record. For example, an invoice-line definition might contain a description, quantity, unit price, total, and a link to its invoice.
The definition is the shared contract used by forms, stored rows, links, validation, and computed fields. Good definitions make all of those parts predictable; poor definitions force every client to invent its own interpretation.
Start with the record, not the screen
Section titled “Start with the record, not the screen”Define one table around one kind of thing:
| Table | One row represents |
|---|---|
customer |
One customer |
invoice |
One invoice header |
invoice_line |
One line belonging to an invoice |
product |
One product |
Do not copy all customer fields into every invoice just because one screen displays them together. Store the customer once and link the invoice to it. Create a separate child table when a record can contain a repeated collection, such as invoice lines.
Profiles keep related tables together
Section titled “Profiles keep related tables together”An ordinary profile contains the tables and records for one accounting-period workspace. A profile table may link to another table in that profile or to a table in the global profile, but never directly to another ordinary profile’s table. A global table may link only to another global table. See Accounting-period profiles and the global profile for the scope model.
The profile also has one accounting currency. This is not necessarily the currency of every money column. A table may contain EUR and USD money fields while the profile keeps its books in EUR.
What a definition controls
Section titled “What a definition controls”A definition decides:
- which columns a row has;
- the type and meaning of each value;
- which values are required;
- which columns link to rows in other tables;
- how a row is named in pickers;
- which columns are indexed for repeated lookup;
- which values are managed by the server instead of edited directly.
The server adds row identity, deletion state, creation time, and revision tracking automatically. Do not model those as user columns.
A practical invoice model
Section titled “A practical invoice model”An invoice is naturally split into a header and repeated lines:
invoice├── number: TEXT, required├── customer: LINK(customer), required├── issue_date: DATE, required└── total: MONEY(EUR)
invoice_line├── invoice: LINK(invoice), required├── description: TEXT, required├── quantity: DECIMAL(12,3), required├── unit_price: MONEY(EUR), required└── line_total: MONEY(EUR)This structure answers important questions without client-specific rules:
- every line belongs to exactly one invoice because its link is required;
- quantity supports fractional units because it is decimal;
- prices and totals carry an explicit currency;
- invoice and customer pickers can use meaningful display columns;
line_totalandinvoice.totalcan later be backed by Steel calculations without changing their storage types.
Row display columns
Section titled “Row display columns”Row display columns define how another part of the application identifies a row. A customer might use name; an invoice might use number followed by issue_date.
Choose fields that are short, recognizable, and reasonably stable. Avoid long notes, calculated balances, or values that change constantly. If no display columns are selected, the row is identified only by its numeric ID.
Links express relationships
Section titled “Links express relationships”A link column stores the ID of a row in another table and gives the relationship its name.
invoice.customer → customerinvoice_line.invoice → invoiceThe name matters. If an invoice links to the same address table twice, call the links something meaningful such as billing_address and delivery_address. Consumers can then tell which relationship to follow without guessing.
Links are also used by pickers, related aggregates, hydration, and quantity effects. See Columns and links for the detailed rules.
Values managed by the server
Section titled “Values managed by the server”Some returned columns are generated or read-only. Examples include parsed phone and IBAN components, accounting fields, accounting-transfer source values, and quantity-ledger balances.
A client should respect the behavior returned with the definition:
- show generated values when they help the user;
- disable editing when a value is read-only;
- submit only fields the user is allowed to control;
- never infer editability only from a column name or type.
Generated and read-only are separate properties. A generated companion may still be writable in some cases, so both flags matter.
Changing an existing definition
Section titled “Changing an existing definition”Adding columns
Section titled “Adding columns”Columns can be appended to an existing user-defined table. Existing columns keep their identity, order, data, and type.
Before adding a required column to a table that already has rows, decide how existing records will obtain a valid value. Definition changes should reflect a data-migration plan, even when the structural operation itself is accepted.
Accounting and accounting-transfer definitions must be chosen when the table is first created. They cannot be bolted onto an existing table because they provision several coordinated fields and supporting structures.
Renaming a column
Section titled “Renaming a column”A rename changes the public alias while preserving the physical column and its data. Links, validation, and stored calculations continue to point at the same underlying field.
The rename still changes the client-facing contract. Refresh forms, imports, saved mappings, and other configuration to use the new alias. Rename history is available when old saved configuration must be reconciled.
What cannot be changed in place
Section titled “What cannot be changed in place”The definition service is not a general schema editor. It does not provide ordinary operations for:
- changing an existing column’s type;
- reordering existing columns;
- removing individual columns;
- converting an ordinary column into a link;
- changing a link to target another table.
Treat those as explicit data-migration work rather than silently changing what existing values mean.
Deleting a table
Section titled “Deleting a table”A user-defined table can be removed only when it has no rows. Deleting it removes the table structure, not merely the rows inside it. Check incoming relationships and attached behavior before deletion.
Protected system tables are owned by their backend feature and cannot be changed or deleted through general table-definition operations.
Copying a profile
Section titled “Copying a profile”Copying a profile creates the same structure without copying its row data. This is useful for turning a known-good profile into a template for another accounting entity or period.
The copy preserves columns, links, indexes, validation, accounting configuration, and attached Steel scripts. Relationships and script dependencies are resolved against the copied tables rather than continuing to point at the source profile.
Continue with columns
Section titled “Continue with columns”Columns and links explains how to choose types, when fields are generated or read-only, how links behave, and which columns make useful indexes and row labels.