Custom tables live in the same per-project schema as your built-in data, so
they share your project’s isolation and connection. They are addressed through
a dedicated
/db surface that only ever touches custom tables — built-in
and bundle tables are never reachable through it.The custom_ invisibility model
Every custom table is stored with a custom_ prefix on its physical name, but you never type that prefix. You work with logical names everywhere:
- You create a table called
Events→ Sublay stores it physically ascustom_Events. - You read and write rows by calling
client.table("Events")→ Sublay resolves it tocustom_Events. - The dashboard displays it as
Events.
/db surface can never reach a built-in table. A request for a built-in name like Comments resolves to custom_Comments, which doesn’t exist, so it 404s — the built-in Comments table stays unreachable.
Managed columns
Every custom table is created with a set of server-managed columns that you never write to directly:| Column | Type | When present | Purpose |
|---|---|---|---|
id | uuid | Always | Primary key, defaulted to gen_random_uuid(). |
createdAt | timestamptz | When timestamps: true | Set on insert. |
updatedAt | timestamptz | When timestamps: true | Bumped automatically on every update. |
deletedAt | timestamptz | When paranoid: true | Soft-delete marker. null for live rows. |
id, createdAt, updatedAt, or deletedAt is refused). deletedAt is read-visible — you can filter and sort by it and surface it with includeDeleted — but never writable.
Timestamps and soft-delete
Two flags, set when you create the table, control the managed columns:timestamps(defaulttrue) — emitscreatedAt/updatedAt. With timestamps on, the default sort for reads iscreatedAt desc; with timestamps off, the default sort isid.paranoid(defaultfalse, requirestimestamps) — emitsdeletedAtand turns deletes into soft deletes. A delete setsdeletedAtinstead of removing the row; soft-deleted rows are excluded from reads by default and resurface only withincludeDeleted. ArestoreclearsdeletedAt. Passforce: trueto a delete to hard-delete a paranoid row.
deletedAt column, so every delete is a hard delete.
Column types
A custom column is one of nine logical types:| Logical type | Physical SQL type | Notes |
|---|---|---|
text | TEXT | |
integer | BIGINT | 64-bit integer. |
float | DOUBLE PRECISION | |
decimal | NUMERIC | Arbitrary-precision number. |
boolean | BOOLEAN | |
date | DATE | Calendar date, no time. |
timestamp | TIMESTAMPTZ | Timestamp with time zone. |
uuid | UUID | |
json | JSONB |
nullable flag and an optional defaultValue (validated against the column’s type).
Surfaces
Custom tables are reachable from every Sublay SDK and from the dashboard:Node SDK — rows
client.table(name) — find, create, update, delete, bulk, restore.Node SDK — management
client.tables — create/drop tables and add/drop columns (service-key only).JS SDK — rows
client.table(name) row operations from any browser or JS runtime.React hook
useTable(name) for React and React Native.The dashboard data editor
The dashboard ships a full data editor for custom tables, under the project’s Database view:- Schema management — create a table (with the full type set, per-column nullable/default, and the
timestamps/paranoidtoggles), add a column to an existing table, and drop tables or columns. Drop actions open a confirmation dialog that previews what will be lost (e.g. the affected row count) and gate the action behind typing the table/column name. - Row editing — insert and edit rows through type-aware inputs. Managed columns are not editable. (Row editing is offered for custom tables only — built-in tables stay read-only in the editor.)
- Soft delete & restore — on a paranoid table, the grid offers a Show deleted toggle; soft-deleted rows render dimmed with a Restore action.
- Filter, sort, paginate — browse rows with the same filter/sort/pagination contract the SDKs use.
custom_ prefix stripped (Events, not custom_Events).
Permissions. Schema changes (create/drop table, add/drop column) require an
owner or admin role. Row writes (insert, edit, restore) require owner or
editor — the same gate as deleting a row.
Limits
To keep a runaway script from exhausting your schema, custom tables are bounded:- 100 tables per project.
- 100 columns per table.
- 100 rows per bulk create or bulk delete call.

