References & Relations
Reference fields create foreign key relationships between tables.
Single reference
Section titled “Single reference”POST /api/apps/{app}/tables/tasks/fields{ "name": "project", "type": "reference", "config": { "target_table": "projects", "on_delete": "cascade", "on_update": "cascade" }}On-delete behavior
Section titled “On-delete behavior”Set on_delete to control what happens to referencing records when a referenced record is deleted. Values are case-insensitive.
| Action | Description |
|---|---|
restrict | Block deletion if referenced records exist (default) |
cascade | Soft-delete the referencing records too |
set_null | Set the reference field to null |
no_action | Take no action |
You can also set on_update (defaults to cascade) using the same set of actions.
Facade enrichment
Section titled “Facade enrichment”When you query records with reference fields, oikapi enriches the response with a display value from the referenced record:
{ "id": "...", "title": "Fix login bug", "project": { "id": "abc-123", "display": "Website Redesign", "type": "reference" }}The display value comes from the referenced table’s facade_field (configured when the field is created). This is resolved with a single LEFT JOIN – no N+1 queries.
Reference arrays (many-to-many)
Section titled “Reference arrays (many-to-many)”POST /api/apps/{app}/tables/tasks/fields{ "name": "assignees", "type": "reference_array", "config": { "target_table": "system_users" }}Reference arrays store their links in junction tables behind the scenes. Each referenced record gets facade enrichment:
{ "assignees": [ {"id": "user-1", "display": "Alice", "type": "reference"}, {"id": "user-2", "display": "Bob", "type": "reference"} ]}By default, deleting a referenced record removes its ID from the array (the array’s on_delete defaults to remove_reference). You can also set min_items and max_items in the config to constrain the number of links.
Self-references
Section titled “Self-references”Tables can reference themselves for hierarchical data:
POST /api/apps/{app}/tables/categories/fields{ "name": "parent", "type": "reference", "config": { "target_table": "categories" }}File references
Section titled “File references”file and file_array fields reference uploaded files:
POST /api/apps/{app}/tables/tickets/fields{ "name": "attachments", "type": "file_array"}Reference search (autocomplete)
Section titled “Reference search (autocomplete)”Use find_refs to search for records to populate a reference field dropdown:
GET /api/apps/pm/tables/projects/records?find_refs=websiteReturns matching records with their display values, suitable for autocomplete/dropdown UIs.
Saved views
Section titled “Saved views”A saved view (called a preset in the API) is a named, reusable presentation of a table’s records: a filter, a sort order, and a layout. Views are the primary surface for working with records day to day — the same table can be shown as a spreadsheet-style grid, a kanban board, a calendar, or a topology graph, each saved as its own view.
Views are scoped to a single table and stored per application. Manage them under the table’s presets route:
GET /api/apps/{app}/tables/{table}/presets # list views for a tablePOST /api/apps/{app}/tables/{table}/presets # create a viewGET /api/apps/{app}/tables/{table}/presets/{name} # get one viewPATCH /api/apps/{app}/tables/{table}/presets/{name} # update a viewDELETE /api/apps/{app}/tables/{table}/presets/{name} # delete a viewList responses are nested under presets; single-view responses under preset.
Creating a view
Section titled “Creating a view”A view is a filter and sort plus an optional layout block. The layout.type selects the display mode.
POST /api/apps/pm/tables/tasks/presets{ "name": "open-tasks", "display_name": "Open Tasks", "icon": "ListChecks", "filter": "eq(status,'open')", "sort": [{"field": "due_date", "direction": "asc"}], "layout": {"type": "grid"}}A view can be filter/sort only (omit layout, and the client is free to switch display modes), or it can pin a specific layout. Set is_default: true to make a view the default for its table.
View types
Section titled “View types”The layout.type is one of:
| Type | Display | Key layout fields |
|---|---|---|
grid | Spreadsheet-style rows and columns (the default) | aggregates, highlightRules, quickFilters |
kanban | Cards in columns, grouped by a field, drag-to-reorder | groupBy, cardLayout, orderScope, columnWidth |
grouped | Grid grouped into collapsible sections | groupBy |
calendar | Records placed on a calendar by a date field | dateField, titleField |
timeline | Records on a horizontal time axis with start/end | dateField, endDateField, titleField |
graph | Node-and-edge topology diagram over an edge table | edgeTable, via, direction, depth, labelField, rankdir |
reconciliation | Two-sided matching workbench (e.g. bank reconciliation) | — |
Common layout options apply across types: highlightRules (conditional row/card coloring), aggregates (footer/header sums and counts), and quickFilters (filter chips shown above the view).
Kanban
Section titled “Kanban”A kanban view groups cards into columns by an enum, boolean, or reference field (groupBy) and lets users drag cards between and within columns. cardLayout maps fields to card zones (title, topRight, body, bottomLeft, bottomRight):
{ "type": "kanban", "groupBy": "status", "cardLayout": { "title": "name", "topRight": "story_points", "body": "description", "bottomRight": "assignee" }}Manual card ordering is persisted separately through the table’s kanban-order endpoint:
GET /api/apps/{app}/tables/{table}/kanban-order # positionsPUT /api/apps/{app}/tables/{table}/kanban-order # bulk setPATCH /api/apps/{app}/tables/{table}/kanban-order # reorder one cardA graph view renders a node-and-edge topology by traversing a homogeneous edge table (see References & Relations for how records link). It names the edge table, the edge types to follow (via), a direction (downstream, upstream, or both), a max depth, and the field to use as each node’s label. Its subgraph is served by the traversal engine:
GET /api/graph/traverse?app={app}&edge={edge_table}&seed={id}&via={type}&direction=downstream&depth=3Applying a view to a record list
Section titled “Applying a view to a record list”Pass a view’s name as the view query parameter when listing records; the server applies that view’s filter and sort:
GET /api/apps/pm/tables/tasks/records?view=open-tasksColumn visibility is handled client-side; the view parameter applies the saved filter and sort only. You can still layer additional filter and sort query parameters on top.