Administration

Custom fields

Custom fields let an admin attach workspace-specific metadata to any of Vigilo's core entities — changes, incidents, tasks, assets, and projects — without…

Last updated

Overview

Custom fields let an admin attach workspace-specific metadata to any of Vigilo's core entities — changes, incidents, tasks, assets, and projects — without modifying the schema or shipping a release. A field is declared once (the FieldDefinition), populated per record (the FieldValue), and tracked over time (the FieldValueHistory). Field-level read/write permissions let admins expose internal-only fields without leaking them to broader audiences, and computed fields run safe-eval expressions over other fields and entity data.

Custom fields live under Settings → General → Custom fields. The configured fields render on the create dialog, the edit dialog (where applicable), and the entity's detail panel — automatically wired up for each of the five supported entity types. Edit dialogs pre-fill with the entity's saved values; new entities persist their custom field values immediately after creation.

Why it exists

Every customer has a small handful of fields they want on every change, every incident, every asset that don't fit the universal model: a compliance code, a customer ID, a cost-centre, a vendor name. Adding these fields to the core schema would bloat the product and force every customer to see them; storing them as freeform tags loses the type information. The FieldDefinition / FieldValue split gives each workspace its own custom schema with type safety, history, permissions, and computed expressions, while keeping the shared core schema lean.

Key concepts

FieldDefinition

FieldDefinition (workspace, entity_type, key, label, field_type, is_required, default_value, options (json), formula (string, nullable), readable_by (json list of roles), writable_by (json list of roles), display_order, is_active).

  • entity_type — one of change | incident | task | asset | project. Selects which entity's create / edit / detail surfaces render the field. The five supported types correspond to the most common per-workspace metadata needs; new entity types can be added in a future release.
  • key — slug-form unique identifier (cost_centre, vendor_id). Used in API payloads.
  • label — human-readable form label.
  • field_type — see Supported types below.
  • options — for dropdowns, the choice list [{value, label}].
  • formula — for computed fields, an expression in the safe-eval DSL.
  • readable_by / writable_by — arrays of role names. Empty = visible to all members.

FieldValue

FieldValue (field_definition, entity_type, entity_id, value (json)). One row per (field, entity). The value column is JSONB so type-specific serialisation is handled at write time (numbers as numbers, dates as ISO-8601 strings, dropdowns as the option value). Reading a field renders the value through the type's React component (number → formatted number, date → date picker, dropdown → label lookup).

FieldValueHistory (audit)

Every write to a FieldValue appends a row to FieldValueHistory (WB.33: field_value, old_value, new_value, changed_by, changed_at, change_reason). The entity detail page exposes a Field history drawer that renders the timeline per-field — who changed what, when, and why. Required by SOC 2 evidence collection.

Computed formulas (safe-eval)

Set formula to declare the field as computed — its value is derived from other fields on the entity (and a small set of safe globals: now(), today(), basic arithmetic, string ops) rather than written by a user. Example: a "days_open" field on incident with formula (now() - opened_at).days.

The safe-eval sandbox forbids attribute access (__class__, __import__), function definitions, imports, and any non-allowlisted name. Formulas are evaluated on read (cached per request) so updates are immediate.

Computed fields have writable_by=[] enforced; the API rejects any write attempt with a 400 computed_field_readonly error.

Field-level permissions

readable_by and writable_by accept a list of role names (or custom-role names). The API and UI both gate per-field:

  • If the caller's role is not in readable_by, the field is omitted from the API response and hidden from the UI entirely (not "visible but greyed").
  • If the caller's role is not in writable_by, the field renders read-only with a tooltip.

This enables sensitive-field patterns: a vendor_secret_code field readable only by owner, a compliance_notes field writable only by approver and above.

Supported types

  • text — single-line text input. max_length configurable in options.max_length.
  • textarea — multi-line, no markdown, max 4 000 chars.
  • number — float or int (controlled by options.integer).
  • boolean — checkbox.
  • date — ISO-8601 date (no time component).
  • datetime — ISO-8601 datetime, UTC stored, local rendered.
  • dropdown — single-select from options.choices.
  • multi_select — multi-select from options.choices.
  • url — URL input with validation.
  • user_ref — picker bound to current workspace members.
  • datepicker_with_holidays — example plugin-supplied type that overlays a regional holiday calendar; useful for "planned cutover date" fields where the picker should warn on holidays. Demonstrates how plugins can extend the supported-type list via the FieldType SDK.

Common workflows

Define a new field

  1. Settings → Custom fields → New field.
  2. Pick entity type (e.g. change_request), enter key (cost_centre) and label.
  3. Pick type (dropdown), enter options (["EUR-OPS", "US-OPS", "APAC-OPS"]).
  4. Set required, default value, permissions, display order.
  5. Click Save. The field appears on every change detail page in its order slot.

Backfill existing records

For non-required fields with a default value, existing records render the default until written. For required fields, the system surfaces a banner on existing records — "Required field cost_centre missing" — and blocks state transitions (e.g. submit) until populated.

Add a computed field

  1. Define a field of type number, set formula = "(now() - opened_at).total_seconds() / 3600".
  2. Save. The field renders on every incident with the live computed value.
  3. Attempting to PATCH the field via API returns 400 computed_field_readonly.

View field history

  1. Open the entity detail page, scroll to the Custom fields section.
  2. Click the clock icon next to a field. The drawer slides in with the full FieldValueHistory timeline.

Permissions

  • Owners and admins can create, edit, and deactivate field definitions.
  • All members can read fields they're in readable_by for, and write fields they're in writable_by for. Empty list means "everyone".
  • Engineers and above can read FieldValueHistory; viewers cannot.

Troubleshooting

Field does not appear on the entity page — Likely the user's role is not in readable_by. Check the field definition. If the list is empty, the field is universally visible — clear browser cache.

Formula throws "name 'foo' is not defined" — The formula references a field key that doesn't exist on the entity. Double-check the key matches.

Field history is empty after multiple writes — Confirm the writes went through the API (PATCH /api/v1/{entity}/{id}/) and not via direct DB. Direct DB updates do not append history.

Plugin-supplied type ("datepicker_with_holidays") missing from picker — The plugin must be enabled for the workspace under Settings → Integrations → Plugins and the React bundle loaded. Reload the page after enabling.

Required field blocks state transition — Populate the field. If the field was added after the entity was created, the API returns 400 required_field_missing on transition; users must update the field first.

Computed field shows stale value — Computed fields are evaluated on read with a request-scoped cache; navigate away and back to recompute. Server-side caches do not persist across requests.

Related