Custom Tables

Tables native to Wallabi, not pulled in from an external system.

Custom tables let you create and manage structured data directly inside a Wallabi data source. They’re useful when your apps need a small, hand-managed dataset alongside the data coming from your databases and applications — mapping tables, classification lookups, configuration values, or any reference data your transforms need to join against.

When to Use a Custom Table

Custom tables are a good fit when:

  • You need a lookup table — priority codes, status mappings, category labels
  • The data doesn’t live in a source system — it’s something you or your team curate by hand
  • You want your reference data alongside your apps — tracked and versioned, not scattered across spreadsheets

If your data already lives in a database, application, or file, connect it as a data source instead.

How They Fit In

A custom table lives inside one of your data sources, alongside its external tables and files, and shares that data source’s access rules. It appears in the Data Explorer, joins with other tables in transforms, and is queried the same way as any other table. The only difference is that the data comes from you rather than an external connector.

Once a custom table is in place, a transform can use it like any other table:

SELECT t.*, pm.label AS priority_label
FROM tickets t
JOIN priority_mappings pm ON t.priority = pm.code

Key Behaviors

Full edit history

Every change to a custom table is tracked — both schema changes and data changes. Nothing is ever permanently overwritten. When you “delete” a row, it’s hidden but can be restored later. You always have a record of who changed what and when.

Rename-safe columns

Renaming a column doesn’t break anything. Existing data stays intact, and queries referencing the old name continue to work — Wallabi keeps the old name as an alias.

Dev and production isolation

Schema changes and row edits made in your dev environment are scoped to that environment only. Production data is always visible while you work, but experimental changes don’t affect production until you promote them. This lets you try things out safely — add a column, load some test rows, see how a transform behaves — without risk.

The flip side is that your columns do not travel with a code deploy — they are promoted separately, and before the code that reads them. See Promoting custom-table changes for how that works, including what happens when two branches touch the same column, and why dev-scoped rows are never promoted.

Column Types

Custom tables support six column types:

TypeExampleNotes
Text"Acme Corp"
Integer42Whole numbers — the right type for ids and join keys
Number42.5Decimal numbers
Booleantrue
Date2026-04-15A calendar day, with no time attached
Date and time2026-04-15T14:30:00ZAn instant, stored and returned in UTC

Defaults and required columns

Any column can have a default value, filled in for every row that doesn’t set the column — both on new rows and on existing rows when the column is first added. A column can also be marked required, which rejects any write that would leave it empty. A required column needs a default, so adding one to a table that already has rows always succeeds.

Keys and Relationships

Custom tables can declare the same integrity rules you’d expect from a database, so bad data is refused on the way in rather than discovered later in a dashboard.

Unique keys

A unique key names the column (or set of columns) that identifies a row — a table’s grain. A priority_mappings table might be keyed on code; a monthly targets table on region and month together. Once a key is declared:

  • Duplicates are refused. Any write that would create a second row with the same key is rejected.
  • Updates become upserts. Loading a batch of rows by that key updates the rows that already exist and inserts the ones that don’t — so re-running the same load is safe and changes nothing the second time.

You can declare a unique key when you create a table or add one later. Adding a key to a table that already contains duplicates is refused, and Wallabi shows you the offending rows so you can clean them up first. Date-and-time columns can’t be part of a key.

Foreign keys

A foreign key links a column in one custom table to a row in another custom table in the same data source — pointing at either that table’s unique key or its built-in row id. A write that names a row that doesn’t exist is refused, so an account_tiers table can never reference a tier that isn’t in tiers.

Each foreign key also says what happens when the row it points at is deleted:

On deleteWhat happens to the rows pointing at it
CascadeThey’re removed too (each keeps its history)
RestrictThe delete is refused while any row still points at it
Set nullTheir linking column is cleared (only allowed on columns that aren’t required)

As with unique keys, adding a foreign key to a table whose existing rows reference missing values is refused, with those rows reported back to you.

Working with Custom Tables

Custom tables are created and managed through Claude Code. Ask it to:

  • Create a table — “Create a priority mappings table with code, label, and sort order columns”
  • Add reference data — “Add these priority rows: P1 Critical, P2 High, P3 Medium”
  • Modify the schema — “Add a description column to the priorities table”
  • Update values — “Change the label for P1 to Urgent”
  • Load a batch of rows — “Upsert these 200 rows from the spreadsheet into targets, keyed on region and month”
  • Add constraints — “Make code unique in priorities, and point tickets_overrides.priority at it”
  • Remove rows — “Remove the P4 priority row”

Because Claude Code understands your data sources and your transforms, it can often suggest a custom table on its own — for example, when a transform contains a hard-coded list of categories that would be easier to maintain as a table.

A Typical Workflow

Setting up a priority mapping table might look like this:

  1. Create the table — ask Claude Code to create priority_mappings with code, label, and sort_order columns, with code as its unique key
  2. Add the reference rows — ask Claude Code to insert the priority values
  3. Use it in a transform — join tickets to the mapping to get human-readable labels
  4. Adjust over time — rename a label, add a new priority, or hide a column as your needs evolve — re-loading the list by code updates what changed and leaves the rest alone

Each change is captured in the table’s history, so nothing is lost if you need to look back or undo.