Skip to content

Validation (Server Perspective)

Summary

Validation runs at three levels. You control all of them. This page explains what to put where.

The three layers

Layer 1: Local validation rules (client-side, instant)
  Your server defines rules in the field's `validation` object.
  The client enforces them without a network call.

Layer 2: Blur validation (server-side, per-field)
  For lookup fields with `validate` endpoint.
  The client calls your server when the user leaves the field.

Layer 3: Save validation (server-side, full changeset)
  Your save handler validates the complete changeset.
  The final gate before data is persisted.

Layer 1: Local validation rules

Add a validation object to the field definition. The client enforces these rules instantly when the user confirms a field edit:

json
{
  "id": "name",
  "label": "Name",
  "type": "Text",
  "validation": {
    "required": true,
    "max_length": 100
  }
}

If validation fails, the user is locked on the field until they fix the value or press Esc to revert.

Available rules

RuleTypeWhen to use
requiredbooleanField must not be empty
max_lengthintegerMaximum character count
min_lengthintegerMinimum character count
patternregexValue must match pattern (e.g., email format)
input_maskstringFilter keystrokes: "uppercase", "lowercase", "digits_only"
min / maxfloatNumeric bounds for Decimal/Integer fields
decimalsintegerMaximum decimal places

What to put here

  • Format constraints (length, pattern, character filtering)
  • Required fields
  • Numeric bounds
  • Anything that can be checked without server access

What NOT to put here

  • Uniqueness checks (requires database)
  • Relational validation (use blur validation)
  • Cross-field rules (use save validation)

Layer 2: Blur validation

For fields bound to a related table (lookup fields), add a validate endpoint to the lookup definition:

json
{
  "lookup": {
    "endpoint": "/lookup/post_code",
    "validate": "/validate/post_code"
  }
}

See Lookups for the full specification.

What to put here

  • "Does this value exist in the related table?"
  • Autofill values (fill related fields automatically)

What NOT to put here

  • Business rules that depend on other fields
  • Rules that depend on the full save context

Layer 3: Save validation

Your save handler receives the full changeset. Validate everything:

json
POST /screen/customer_card/10000/save
{
  "changes": {
    "name": "",
    "post_code": "100"
  }
}

If validation fails, return HTTP 200 with the error in status:

json
{
  "layout": "Card",
  "status": "Cannot save: Name is required.",
  "sections": []
}

What to put here

  • Cross-field validation (e.g., "end date must be after start date")
  • Business rules (e.g., "credit limit exceeded")
  • Uniqueness constraints
  • Permission checks
  • Anything that requires the full changeset or database state

Don't duplicate validation

Your validate endpoint and save handler should share the same underlying checks. Don't implement postal code validation in two places — extract it into a shared function.

Error messages

  • Be specific: "'999' is not a valid postal code." not "Validation failed."
  • Include the rejected value when appropriate
  • Use the field label: "Name is required." not "Field is required."