Column Rules
Column Rules Column Rules let you attach extra validation constraints (min/max, length, pattern, format, etc.) to a column without writing any code. Rules are enforced server-side every time the API receives a POST or PATCH against the table — invalid payloads are rejected with H
Column Rules
Column Rules let you attach extra validation constraints (min/max, length, pattern, format, etc.) to a column without writing any code. Rules are enforced server-side every time the API receives a POST or PATCH against the table — invalid payloads are rejected with HTTP 400 and a list of human-readable error messages.
Rules are configured entirely from the admin UI; there is no manual record creation needed.
When Rules Run
Rules apply to body validation on the dynamic REST endpoint of the table:
POST /<table_name>(create)PATCH /<table_name>/<id>(update)
They run only when the table has validateBody = true (the default for new tables). If validateBody is off, rules are skipped — the server only enforces basic shape (type, nullability) at the persistence layer.
Rules do not run for:
- GET requests
- Custom routes that bypass the standard CRUD pipeline
- Records inserted directly via repository calls inside handlers/hooks (those are server-trusted)
Toggling validateBody on a Table
- Open Collections > [your table] (or any system table you own).
- Open the table form (gear / edit).
- Toggle Validate Body on or off.
- Save.
Default for newly-created tables is on.
Adding a Rule to a Column
Rules live next to columns in the table editor.
- Open the table in Collections (or Settings > Tables for system tables).
- Locate the column row in the columns table.
- Click the ruler icon in the column row (next to the field-permission shield icon).
- The Manage Rules modal opens with the existing rule list for that column.
- Click Add Rule.
- Pick a Rule Type from the dropdown (only types that make sense for the column's data type are shown; see the matrix below).
- Fill the Value field — the editor adapts to the rule type (number input, regex + flags, format dropdown).
- Optionally fill Message to override the default error text.
- Toggle Enabled (defaults to on).
- Click Save.
Rule Type Reference
| Rule Type | Applies To | Value Shape | Effect |
|---|---|---|---|
min |
int, bigint, float, decimal | { v: number } |
Number must be ≥ v |
max |
int, bigint, float, decimal | { v: number } |
Number must be ≤ v |
minLength |
varchar, text, richtext, code, uuid | { v: number } |
String length must be ≥ v |
maxLength |
varchar, text, richtext, code, uuid | { v: number } |
String length must be ≤ v |
pattern |
varchar, text, richtext, code | { v: string, flags?: string } |
Must match RegExp(v, flags) |
format |
varchar, text, code | { v: 'email' \| 'url' \| 'uuid' \| 'datetime' } |
Must match the named format |
minItems |
array-select | { v: number } |
Array length must be ≥ v |
maxItems |
array-select | { v: number } |
Array length must be ≤ v |
custom |
any | (free-form) | Reserved — currently passes through; use a pre-hook for fully custom logic |
The Manage Rules modal hides incompatible rule types per column and prevents adding the same rule type twice (except custom).
Important: Rules Are Additive, Not Replacement
Rules never replace the column's built-in checks. The server always enforces (in this order):
- Type (from
column.type— int, varchar, boolean, etc.) - Nullability (from
column.isNullable— null is rejected iffalse) - Length cap for
varchar(fromoptions.length) - Then your rules, on top of the above
So you cannot use a rule to make a field nullable, change its type, or remove the length cap — those are properties of the column itself. Edit the column to change them.
There is no required rule type. Required-ness comes from isNullable = false on the column, plus whether the column has a default value.
Disabling vs Deleting a Rule
- Disable (toggle off) — keeps the rule record but stops enforcement. Useful for temporary debugging.
- Delete (trash icon) — removes the rule completely.
Cache invalidation happens automatically; the new validator schema is rebuilt within ~50 ms across all instances.
Custom Error Messages
When a rule fails, the response body lists each violation as a string. By default the message is generated from the rule type and value (e.g. "name: String must contain at least 3 character(s)").
Set the Message field on the rule to override the text. Plain strings — no template variables.
Error Response Shape
A failed validation returns:
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"statusCode": 400,
"message": [
"name: String must contain at least 3 character(s)",
"email: Invalid email"
],
"error": "Bad Request"
}
message is always an array of strings. Front-end forms can iterate and bind each entry to its corresponding field (the prefix before : is the field name).
Cascade Validation (Nested Create)
When the table accepts nested records (e.g. POST /post with an inline comments: [{ body: '...' }]), child rules from the related table are validated too — provided the related table also has validateBody = true. Connect-by-id payloads ({ author: 5 } or { author: { id: 5 } }) skip nested validation since no new record is being created.
System Tables
Some system tables (table_definition, field_permission_definition, etc.) accept a small set of virtual fields that aren't real columns (graphqlEnabled, config). The validator allows those by name; everything else outside the schema is rejected as "not allowed". You don't need to do anything special for system tables — rules work the same way.
Related
- Server-side: validation is implemented in
buildZodFromMetadata(src/shared/utils/zod-from-metadata.ts). Rules are cached inColumnRuleCacheService. - Field-level read/write access: see Field Permissions.
- Schema confirm/preview when editing a table: see Schema migration preview.