Custom fields
Custom fields are the lab's own fields for its jobs, like custom fields in ClickUp or Jira. An owner or admin makes them in Settings, Custom fields. Everyone can set a job's value. A value lives only in Flow: a change never writes to the work system (Seazona) or the accounting system (Xero).
Code: server/core/custom-fields.js. Screen: Settings, Custom fields
(server/ui/custom-fields.js). API: server/api/custom-fields.js. On the
Jobs page, a job's panel shows its fields to change, and the board can be
grouped by a dropdown, person or checkbox field and moved by drag
(docs/JOB_BOARD_MOVES.md).
The screen
Settings, Custom fields is laid out like ClickUp's Custom Field Manager:
- Toolbar: search (names and option names), Field type (show some types only), Filter (Archived), and New.
- Table: one group per type, in name order. A group's header shows the type's icon, name and count, and folds the group open and closed. An owner or admin clicks the group's pill to give the group a colour. Folded groups stay folded in that browser; a search opens every group with a match. Each group ends with "Create type field".
- Row: the field's name, its type as an icon in its colour, who made it, the day it was made, and where it applies (Jobs). Rows do not show options. Under the pointer, Edit opens the sidecar. The type icon picks the field's colour. The ⋯ menu has Edit, Rename (in place), Colour, Duplicate, Convert (not in Flow yet), Copy custom field ID, Archive and Delete. An archived field's menu has Bring back and Delete.
- Sidecar: like ClickUp's, a panel that slides in from the right of the manager, below the toolbar: the type in its head (a new field picks it there), the name, a dropdown's options as a list (each with its colour dot; Add option), where the field belongs, and Delete, Cancel and Save at its foot. The field's colour is not in the sidecar: the row's type icon and ⋯, Colour change it. The table stays usable behind it: another row's Edit, or Create, switches the sidecar at once. Save, Cancel, × and Esc close it.
- Colours: every colour picker (a group, a field, an option) shows the real pill in each colour, so a person sees the result before they pick. A field with no colour of its own takes its group's colour.
- Delete: asks first and says how many jobs have a value. Delete takes the field, its values and its history for good; Archive keeps them.
- From system: the work system's own fields (Seazona's stage), read only, in their own group. View opens the sidecar, read only, with the stages as a list, in board order, each dot in its board colour. Its Date created is the day the system was connected: the first time its settings were saved in Flow, or, for a system set up in the host's own settings, the first time Flow saw its work.
- On a phone the table keeps the name, the type icon and the menu.
Icons come from Lucide (lucide-static, ISC), served one file each at
/assets/icons/<name>.svg, so any page can use any Lucide icon.
The parts
| Part | What it is |
|---|---|
| Field | One row in job_fields: a name, a type, a colour (or none: its group's), and for a dropdown its options in lane order. |
| Value | One row in job_field_values: one job's value for one field. A job is the work system's job, (source_system, source_job_ref), the same key the Jobs board uses. |
| Event | One row in job_field_events: who changed a value, from what, to what, and when. |
| System field | A work system's own field, read only. Today: each work system's job statuses (its stage), from the port's listWork. |
Types
| Type | Value | Board lanes |
|---|---|---|
| Dropdown | One option id | Yes, one lane per option |
| Person | One active person's id | Yes, one lane per person |
| Checkbox | true or false |
Yes |
| Date | YYYY-MM-DD |
No |
| Number | A number | No |
| Text | Up to 500 characters | No |
Every lab has an Assignee: a person custom field Flow makes once, the
first time the lab's custom fields are read and the lab has no person
field (live or archived). The lab can rename it or archive it; Flow does
not make it again, also when the lab deletes it
(job_field_settings.assignee_made_at). The Jobs list shows the first
person custom field as the Assignee column, and the filter "Assignee is Me".
Rules
- A field's type is fixed once it exists.
- Names are unique among live fields, with no regard to case. An organisation has 50 live fields at most, and a dropdown 50 options.
- An option a person removes is kept as archived, so a job that still has it reads its name. A new value cannot use it.
- Archive keeps a field's values. Bring back restores them.
- A value of
nullclears the value.
Moves and conflicts
A drag between lanes sets one value with expectedUpdatedAt: the
updatedAt of the value the person last saw, or null when they saw none.
When someone changed it since, Flow changes nothing and answers 409 with
the current value in details.current. The board then shows the current
value. This copies OpenClaw's workboard: the server's copy wins.
API
| Route | Who | What |
|---|---|---|
GET /api/custom-fields[?archived=1] |
Signed in | Fields (each with color and valueCount), system fields (with createdAt, when the system was connected), the people a person field can name (active people, by name), types, groupable types, option colours, groupColors |
POST /api/custom-fields |
Owner, admin | Make a field: { name, type[, options, color] } |
PUT /api/custom-fields/order |
Owner, admin | { ids }: every live field, in the new order |
PUT /api/custom-fields/groups/:type |
Owner, admin | { color }: a type group's colour; null puts back its own |
PUT /api/custom-fields/:id |
Owner, admin | { name?, options?, color? }; color: null takes the group's colour |
POST /api/custom-fields/:id/archive |
Owner, admin | { archived } |
DELETE /api/custom-fields/:id |
Owner, admin | Delete the field, its values and its history |
GET /api/custom-fields/values |
Signed in | Every value of the live fields |
PUT /api/custom-fields/values |
Signed in | { sourceSystem, sourceJobRef, fieldId, value[, expectedUpdatedAt] } |
GET /api/custom-fields/history?sourceSystem=&sourceJobRef= |
Signed in | One job's changes, newest first |