Skip to content

References & Relations

Reference fields create foreign key relationships between tables.

POST /api/apps/{app}/tables/tasks/fields
{
"name": "project",
"type": "reference",
"config": {
"target_table": "projects",
"on_delete": "cascade",
"on_update": "cascade"
}
}

Set on_delete to control what happens to referencing records when a referenced record is deleted. Values are case-insensitive.

ActionDescription
restrictBlock deletion if referenced records exist (default)
cascadeSoft-delete the referencing records too
set_nullSet the reference field to null
no_actionTake no action

You can also set on_update (defaults to cascade) using the same set of actions.

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.

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.

Tables can reference themselves for hierarchical data:

POST /api/apps/{app}/tables/categories/fields
{
"name": "parent",
"type": "reference",
"config": {
"target_table": "categories"
}
}

file and file_array fields reference uploaded files:

POST /api/apps/{app}/tables/tickets/fields
{
"name": "attachments",
"type": "file_array"
}

Use find_refs to search for records to populate a reference field dropdown:

GET /api/apps/pm/tables/projects/records?find_refs=website

Returns matching records with their display values, suitable for autocomplete/dropdown UIs.

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 table
POST /api/apps/{app}/tables/{table}/presets # create a view
GET /api/apps/{app}/tables/{table}/presets/{name} # get one view
PATCH /api/apps/{app}/tables/{table}/presets/{name} # update a view
DELETE /api/apps/{app}/tables/{table}/presets/{name} # delete a view

List responses are nested under presets; single-view responses under preset.

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.

The layout.type is one of:

TypeDisplayKey layout fields
gridSpreadsheet-style rows and columns (the default)aggregates, highlightRules, quickFilters
kanbanCards in columns, grouped by a field, drag-to-reordergroupBy, cardLayout, orderScope, columnWidth
groupedGrid grouped into collapsible sectionsgroupBy
calendarRecords placed on a calendar by a date fielddateField, titleField
timelineRecords on a horizontal time axis with start/enddateField, endDateField, titleField
graphNode-and-edge topology diagram over an edge tableedgeTable, via, direction, depth, labelField, rankdir
reconciliationTwo-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).

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 # positions
PUT /api/apps/{app}/tables/{table}/kanban-order # bulk set
PATCH /api/apps/{app}/tables/{table}/kanban-order # reorder one card

A 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=3

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-tasks

Column 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.