Corteksa

Conditional visibility — frontend integration

Conditional visibility — frontend integration

Status: backend shipped on stage (2026-07-27). Frontend not yet built.

What's new

A workspace admin can attach a show/hide rule to a single field so the record form/detail reveals it only when a condition on another field of the same object is met — e.g. show "VAT number" only when "Customer type = Business". Visibility is presentation behavior only: no new field type, no column, no data change. It rides the field's existing options JSON (same blob that holds conditional_requirement).

The full behavior contract is reproduced in Full behavior contract at the bottom of this page — source of truth: src/api/v1/object/fields/CONDITIONAL_VISIBILITY.md.

No new endpoints

The rule is saved through the existing field update/create: PATCH /object/field/:objectSlug/:fieldSlug (and create) with an options.conditional_visibility block. Shape:

"conditional_visibility": {
  "enabled": true,
  "action": "show",                 // or "hide"
  "logic": "AND",                    // AND | OR (multi-condition)
  "conditions": [
    { "trigger_field_slug": "customer_type", "operator": "equals", "values": ["business"] }
  ],
  "clear_on_hide": false             // default: retain the hidden value
}

A single condition may also use the flat shorthand (trigger_field_slug/operator/values); the server normalises it into conditions[]. Operators: equals | not_equals | in | not_in | is_empty | is_not_empty (the same enum as conditional_requirement). All references are slug-first — never a numeric field id. Values for select-family triggers may be sent as option id/value/label; the server canonicalises them.

What the FE needs to do

  • Feature flag: when CONDITIONAL_FIELD_VISIBILITY_ENABLED is off, the server ignores conditional_visibility on read + write and every field is always visible. Render as today.
  • Rule builder (field editor → "Visibility" tab): read as a sentence — "[Show/Hide] this field when [trigger] [operator] [value(s)]". The trigger picker must exclude the field itself and any field that would form a cycle.
  • Live form eval: evaluate each field's rule against the record's current in-form values and show/hide as the user types. Compare by option id, not label. Depth-cap live re-eval and debounce.
  • Server is the source of truth (important): the client's hidden state is a hint only. On submit the server re-evaluates every rule and:
    • a field it computes hidden has its required check skipped and is not validated (so a hidden required field never blocks the save);
    • the hidden field's value is retained (or nulled if clear_on_hide). Do not rely on hiding for correctness or security — a hidden field is still in the API response, export, and PDF.
  • Config-time errors to surface: 422 VISIBILITY_SELF_REFERENCE, 422 VISIBILITY_CYCLE, 400 TRIGGER_NOT_IN_OBJECT, 400 INVALID_OPTIONS.
  • Dangling trigger: if the trigger field was later deleted/deactivated the rule is inert and the field always shows — surface a "trigger missing" warning with a one-click remove.
  • List/table/kanban/export: unchanged — visibility is a form/detail concern.

Not in this backend slice (future work)

  • Live-preview UI, conditional document-template rendering.
  • Field-level permission (restricting who can read a field) — a separate, not-yet-built control. Visibility is not a security boundary.

Full behavior contract

Show or hide a single field in the record form/detail based on a condition evaluated on another field of the same object. Pure presentation behavior — no new field type, no physical column, no data mutation. Gated behind CONDITIONAL_FIELD_VISIBILITY_ENABLED; when off, every field is always visible and required/validation behave exactly as before.

Where it lives

The rule is stored on the target field's existing options JSON as conditional_visibility. It reuses the conditional_requirement operator engine (ValidationEngineService) and the shared operand-resolution util — there is no new evaluation engine.

"conditional_visibility": {
  "enabled": true,
  "action": "show",            // "show" ⇒ visible when met; "hide" ⇒ hidden when met
  "logic": "AND",              // AND | OR fold across conditions (default AND)
  "conditions": [
    { "trigger_field_slug": "customer_type", "operator": "equals", "values": ["business"] }
  ],
  "clear_on_hide": false       // default: retain the hidden value; true ⇒ null it on write
}

A single-condition rule may use the flat shorthand (trigger_field_slug/operator/values); config-time validation folds it into conditions[] and canonicalises operand values to the runtime-comparable form.

Operators (same enum as conditional_requirement): equals | not_equals | in | not_in | is_empty | is_not_empty. Select-family triggers compare by canonical option value/id, never label.

Correctness invariants (server-enforced)

RuleBehavior
Hidden ⇒ not required (FR-3520)A field the server computes as hidden has its required check and its conditional_requirement skipped, so a hidden required field can never dead-end a save. Enforced in both DynamicValidationService (static required) and ValidationEngineService (required-if).
Hidden value (FR-3521)Retained un-validated by default; clear_on_hide: true nulls it on write.
Server is source of truth (FR-3522)On every write the server re-evaluates each rule from submitted data; the client's hidden state is a hint only.
Cycle / self-reference (FR-3530)Rejected at configure time: 422 VISIBILITY_SELF_REFERENCE, 422 VISIBILITY_CYCLE.
Dangling / deactivated trigger (FR-3531)Rule inert ⇒ field visible (fail-open); never crashes.
Fail-open everywhereAny eval error, missing trigger, or empty/disabled rule resolves to visible + validated normally — hiding is what creates the dangerous hidden-required dead-end, so we never hide on a rule we could not evaluate.

Scope

Visibility is a form/detail concern. List/table/kanban/export column layout and the field's physical column + stored value are unaffected — a hidden field still has its column, still holds its value, still exports it.

Hiding is not a security control. A hidden field is still returned by the API, exported, rendered in documents, and writable by workflow/AI. Restricting who can read a field is field-level permission — a separate, not-yet-built control.

Code map

ConcernFile
Rule shapeobject/shared/interfaces/conditional-visibility.interface.ts
Shared operator + operand resolutionobject/shared/utils/condition-evaluator.util.ts
isFieldHidden evaluatorobject/shared/utils/conditional-visibility.util.ts
Feature flagobject/fields/services/visibility-feature.ts
Config-time guard (cycle/self-ref/trigger)object/fields/services/field/utils/validate-conditional-visibility.ts
Write-path skip-required + clear/retainobject/fields/services/validation/dynamic-validation.service.ts
Required-if skip when hiddenobject/fields/services/validation/validation-engine.service.ts
Config wiringfield-create.service.ts / field-update.service.ts

On this page