Validation rule expressions
Validation rules are object-level checks that run on entity instance create,
update, and import. A rule that applies to Record merge decides instead
which records may be merged; see Record merge rules. Each enabled rule holds one boolean expression: it must
return TRUE for the record to save. A FALSE or unknown result blocks the save and
returns the rule’s failure message. Rules run in ascending sort order, before
any C# business logic, and the first failing message is returned.
Choosing the right check
Section titled “Choosing the right check”- Use a field’s
RequiredWhencondition when the only requirement is that one field has a value. - Use a validation rule for everyday consistency checks that describe the record as a whole — required-if rules, ranges, date ordering, text quality, first-level reference checks, regex checks — or when the check compares multiple values, checks a reference, or needs a tailored failure message.
- Use a C# validation function when the logic needs orchestration or goes beyond the expression profile below.
Expression profile
Section titled “Expression profile”A validation expression can use:
- scalar fields of the record’s primary table;
- first-level reference paths such as
Customer.DisplayName; - the variables
@currentUserId,@today,@today+7d, and@today-7d; - the shared operators — comparisons,
AND/OR/NOT, null/empty checks, text operators,IN,NOT IN,BETWEEN; - the validation-only function
MATCHES(value, "regex")(the supported .NET-compatible subset, case-sensitive by default, leading(?i)for ignore-case); - Classifier categories through the portable
CATEGORY(...)literal.
Not supported: EXISTS, ANY, ALL and COUNT collection forms,
aggregate functions, raw SQL, inverse-reference traversal, and deep reference
chains.
MATCHES(Code, '^[A-Z]{3}-[0-9]{3}$')DueDate IS NULL OR DueDate >= StartDateDueDate IS NULL OR DueDate >= @todayAmount <= Customer.CreditLimitCapability = CATEGORY('product-area', 'operations', 'platform')Classifier fields support =, !=, IN, NOT IN, IS NULL, and
IS NOT NULL. Exact and set comparisons do not match null. Field-to-field
equality or inequality requires the same Classifier Catalog. Classifier fields
are supported on the record and through a first-level Reference path. In the
rule editor, place the cursor in the expression and choose Insert category
to insert a portable CATEGORY(...) value; category names and breadcrumbs are
for display only and are not used for matching. The logical root of a
Classifier Catalog is not a category value, while stored non-leaf categories
are valid values. See
Classifier category values.
Regex syntax and limits
Section titled “Regex syntax and limits”Saved validation rules execute in PostgreSQL. MATCHES accepts only patterns
whose behavior agrees with the .NET culture-invariant regex semantics:
characters, dot, character classes (including subtraction), character classes
such as \d, \w, \s and Unicode categories, anchors, capturing and
non-capturing groups, alternation, and admitted greedy/lazy quantifiers.
A leading (?i) applies ignore-case to the whole pattern.
Unsupported constructs return moltaro.dsl.regex.unsupported: lookarounds,
backreferences, balancing groups, word boundaries, other inline options,
non-BMP pattern characters, and unsafe repetition shapes. Patterns are bounded
to 1,024 characters, repetition counts of 255, 512 expanded atoms, 64 distinct
character sets and eight internal anchors. Counted repetitions of groups that
can match empty text are rejected. A malformed .NET pattern returns
moltaro.dsl.regex.invalid. Always validate the exact pattern on the server.
The DSL string parser consumes backslashes before the regex parser sees them:
MATCHES(Code, '^\\d{3}$')MATCHES(Email, '(?i)@example[.]com$')NOT (MATCHES(Code, '^temporary'))In JSON, the first expression is
{"Expression":"MATCHES(Code, '^\\\\d{3}$')"}. A single DSL backslash before
d or . does not preserve a regex escape. Parenthesize MATCHES when
negating it. A null input yields unknown; use Code IS NULL OR MATCHES(...)
when missing text should pass.
Failure messages
Section titled “Failure messages”Message is the required fallback failure-message template, with optional
per-locale translations (en, uk, de, pl, es). Templates use the same
{ … } placeholder style as presentation templates and can render:
- record fields:
{Name}, first-level paths:{Customer.DisplayName}; - system values:
{Number},{DisplayName},{Rule.Name},{Rule.DisplayName},{CurrentUserId},{Today}.
A field referenced only in a failure message still counts as a schema dependency: it blocks field deletion and incompatible type changes the same way an expression reference does.
Authoring workflow
Section titled “Authoring workflow”Validate the expression before saving with the validate-expression operation — the response reports validity plus structured errors (message, token, field path, and stable code). Rules are managed through the validation-rule endpoints of the Configuration API; reordering takes the full active rule set with row versions.
Creating or changing an expression that uses CATEGORY(...) requires Catalog
Reader or management access to the referenced Classifier Catalog. A saved rule
continues to validate records if that access is later removed, and its name,
message, enabled state, and other metadata can still be updated while the
expression remains unchanged.
Disabled rules stay stored but are not evaluated.
Record merge rules
Section titled “Record merge rules”A validation rule with Applies to: Record merge (Scope: 1 in the API;
the default 0 is every save) is not checked on save. It is checked when two
records of the type are merged, and every enabled merge rule must return
TRUE. Without enabled merge rules, merging works as before. Existing rules
keep applying to saves.
The expression reads three versions of the record type’s own fields:
Source: the record that is archived;Survivor: the record that is kept, as it was before the merge;Result: the survivor with the values the merge writes.
Source.Beneficiary = Survivor.Beneficiary AND Source.Direction = Survivor.DirectionResult.Status IS NULL OR Result.Status <> 'Closed'Source.Region IS NULL AND Survivor.Region IS NULL OR Source.Region = Survivor.RegionReferences compare by record identity, not by name, and cannot be followed:
Source.Customer.Name is not valid. Functions, variables, MATCHES,
Classifier fields and collection forms are not available. An empty value
follows the usual rules: Source.Region = Survivor.Region is not TRUE when
both are empty, so write the third form above when two empty values should be
accepted. Choosing the other record as the survivor swaps Source and
Survivor.
The failure message is shown to the person merging. It can use only
{Rule.Name} and {Rule.DisplayName}, never record values. If a rule reads a
field that the person cannot read, the merge is refused with a general message
and the rule is not evaluated, so the answer reveals nothing about the hidden
values.
Merge rules apply in the merge wizard, to the merge API and to C# merges.
Result rules are checked for the values selected in the wizard, before any
save function runs, and again for the saved record after the application’s save
functions, so a function cannot bypass them. A rule must therefore hold for the
selected values too; it cannot rely on a value that only a save function sets. Configure rules in the Constructor on the record type’s
Validation rules tab: choose Applies to: Record merge, write and check
the expression, and save. Through the API, send Scope: 1 when you create a
rule and to the validate-expression operation. See
Record merge API for the
refusal codes.