Validation and conditions
Enum Values
Section titled “Enum Values”Used with string or number properties to render selects:
import type { StringProperty } from "@rebasepro/types";
// Simple: each value mapped to its labelconst simple: StringProperty = { type: "string", name: "Status", enum: { draft: "Draft", published: "Published", archived: "Archived" }};
// As a list, which keeps the order you writeconst listed: StringProperty = { type: "string", name: "Status", enum: [ { id: "draft", label: "Draft" }, { id: "published", label: "Published" }, { id: "archived", label: "Archived" } ]};
// With colors (for Kanban columns and chips)const colored: StringProperty = { type: "string", name: "Status", enum: [ { id: "draft", label: "Draft", color: "grayDark" }, { id: "published", label: "Published", color: "greenDark" }, { id: "archived", label: "Archived", color: "orangeDark" } ]};A plain list of values, enum: ["draft", "published", "archived"], is not part
of the type. A JavaScript config that writes one still works: the database enum,
the generated types and the admin dropdown all read it as those values, each
its own label.
Validation
Section titled “Validation”Every rule lives in the property’s validation block, and which rules a
property accepts depends on its type — min counts characters on a string,
compares magnitudes on a number, and counts elements on an array.
properties: { title: { type: "string", name: "Title", validation: { required: true, min: 2, max: 200 } }, slug: { type: "string", name: "Slug", validation: { unique: true, matches: /^[a-z0-9-]+$/, matchesMessage: "Lowercase letters, digits and hyphens only", trim: true, lowercase: true } }, readingMinutes: { type: "number", name: "Reading time", validation: { integer: true, moreThan: 0, max: 120 } }}Where validation runs
Section titled “Where validation runs”The server checks validation at the request boundary — REST, the realtime
socket and MCP — on what the caller sent, before beforeSave runs, and answers
a write that breaks a rule with a 400 naming every failing field. Server code
writing in-process is trusted and is not checked against these rules:
rebase.data and rebase.dataAsAdmin in a function or cron job, context.data
in a callback, a seed script. What holds there is what the database enforces —
required (NOT NULL), unique (a unique index), an enum’s type, and the width
of a varchar or char column. Anything else your server code writes, check in
the code that writes it.
The admin panel checks the same rules before it saves, in the record form and in the collection table’s inline editor. A table cell judges a value once you edit it: a stored value that fails a rule added later shows no error in the table until someone edits that cell.
Every type
Section titled “Every type”| Rule | Type | Description |
|---|---|---|
required |
boolean |
The value must be present. On a string, "" counts as absent: the API refuses it on every write, a create or an update, with the same required violation (unless the collection has a beforeSave, which may fill it) |
requiredMessage |
string |
What is said when it is not: by the admin form, and as the message of the field’s required entry in the API’s 400. The database’s NOT NULL carries no message |
unique |
boolean |
Only one row in the table may hold this value. The admin form also checks it when saving a value the edit changed, so a duplicate is named on its field before the write |
uniqueInArray |
boolean |
Only one entry per array may hold it. Applies to a direct child of an array property, or a first-level child of a map inside one. Admin form only — not enforced by the API or the database. |
Strings
Section titled “Strings”| Rule | Type | Description |
|---|---|---|
min / max |
number |
Fewest / most characters, inclusive. With columnType: "varchar" or "char", max also sizes the column (VARCHAR(max)), so lowering it on a table that already holds longer rows is a migration the database refuses, and the collection editor warns when an edit lowers it. The default column is TEXT, which max leaves unbounded — the bound is then the server’s request check alone |
length |
number |
Exactly this many characters — a country code, a fixed-width reference |
matches |
string | RegExp |
A pattern the whole value must match. A string is compiled per request, so one that will not compile is rejected at boot rather than quietly becoming no rule |
matchesMessage |
string |
What the form says when matches fails |
trim |
boolean |
Strip leading and trailing whitespace before saving. A transform, not a check — it changes what is written, which is what makes it the fix for “the same tag twice, one with a trailing space”. Admin form only — not enforced by the API or the database. |
lowercase / uppercase |
boolean |
Case-fold the value before saving, like trim. Admin form only — not enforced by the API or the database. |
Email and URL formats are not validation rules. email and url are flags
on the property itself, beside type — statements about the data that the
OpenAPI contract is generated from, rather than form behaviour:
properties: { email: { type: "string", name: "Email", email: true }, website: { type: "string", name: "Website", url: true }}See String properties.
Numbers
Section titled “Numbers”| Rule | Type | Description |
|---|---|---|
min / max |
number |
Smallest / largest accepted value, inclusive |
moreThan / lessThan |
number |
The exclusive twins of min and max |
positive / negative |
boolean |
Strictly above / below zero. 0 is rejected by both — use min: 0 to allow it |
integer |
boolean |
No fractional part. Does not change the column type; columnType does |
| Rule | Type | Description |
|---|---|---|
min / max |
Date |
Earliest / latest accepted date, inclusive. A fixed instant, not “today” |
Arrays
Section titled “Arrays”| Rule | Type | Description |
|---|---|---|
min / max |
number |
Fewest / most elements, inclusive. Elements, not characters |
Conditional Fields
Section titled “Conditional Fields”You can make fields dynamic so they react to the entity’s values. There are two ways to do this:
1. JSON Logic Conditions (Declarative)
Section titled “1. JSON Logic Conditions (Declarative)”You can use the conditions property to define declarative JSON Logic rules that can be serialized and modified visually in the collection editor.
price: { type: "number", name: "Price", conditions: { disabled: { "==": [{ "var": "values.is_free" }, true] }, required: { "!=": [{ "var": "values.is_free" }, true] }, min: 0, clearOnDisabled: true // Set to null if field gets disabled }}The conditions object gives you access to:
disabled,disabledMessage,clearOnDisabled,hidden,readOnlyrequired,requiredMessage,min,maxdefaultValueenumConditions,allowedEnumValues,excludedEnumValuesreferencePath,referenceFiltercanAddElements,sortable(for arrays)acceptedFiles,maxFileSize(for uploads)
disabledMessage is the sentence a reader sees in place of the control. Without
it a greyed-out field explains nothing, which is the difference between a form
that is guiding someone and one that is refusing them.
2. Property Builders (Programmatic)
Section titled “2. Property Builders (Programmatic)”For complex behavior that can’t be expressed via JSON Logic, you can use dynamicProps which evaluates a Javascript function.
price: { type: "number", name: "Price", dynamicProps: ({ values, authController }) => ({ admin: { disabled: values.is_free === true || !authController.user?.roles?.includes("admin") }, validation: values.is_free ? {} : { required: true, min: 0 } })}The same caveat applies, and more sharply: the function is bundled into the panel and called on every render of the form. Admin form only — not enforced by the API or the database.
Next Steps
Section titled “Next Steps”- Properties — the property types these rules apply to
- Defining Collections — where properties are declared
- Security Rules (RLS) — the rules the database enforces, which validation does not replace