DocuTray

Validation Operators

Reference for every operator in the Docutray validation DSL — presence, comparison, numeric precision, text, lists, dates and calculated values — with a runnable example for each.

This page lists every operator the validation DSL accepts, with a runnable example for each. For the syntax around them (when, forEach, define, field references) and how to read the results, see the validation guide.

Every example below runs in Docutray's test suite against the same validator the platform uses, so the Result block is exactly what the API returns, minus value, severity, source and details. Issue messages are in Spanish.

Missing values are skipped

Except for required, exists and notExists, an operator does not run when the field is missing or null: the rule passes. Add required when a value must be present.

Presence

required

The field must exist and must not be null or an empty string "". Fails with REQUIRED_FIELD.

Rules
field.invoice_number required
field.customer.name required
Document
{ "invoice_number": "", "customer": { "name": "Acme SpA" } }
Result
{
  "valid": false,
  "errors": [
    { "field": "invoice_number", "code": "REQUIRED_FIELD", "message": "El campo 'invoice_number' es obligatorio" }
  ]
}

exists

The field must exist and must not be null. Unlike required, an empty string passes. Fails with REQUIRED_FIELD.

Rules
field.notes exists
field.reference exists
Document
{ "notes": "" }
Result
{
  "valid": false,
  "errors": [
    { "field": "reference", "code": "REQUIRED_FIELD", "message": "El campo 'reference' debe existir" }
  ]
}

notExists

The field must be missing or null. Useful inside a when block. Fails with VALIDATION_ERROR.

Rules
field.cancellation_date notExists
Document
{ "cancellation_date": "2024-05-01" }
Result
{
  "valid": false,
  "errors": [
    { "field": "cancellation_date", "code": "VALIDATION_ERROR", "message": "El campo 'cancellation_date' no debe existir" }
  ]
}

optional

Always passes. It documents that a field may be absent; it has no other effect.

Rules
field.purchase_order optional
Document
{}
Result
{ "valid": true, "errors": [] }

Comparison

Comparison operators accept a literal or a reference to another field (field.… or calculated.…). Numbers are compared as numbers even when one side is a string, such as "1.234,56" against 1234.56; see numbers.

equals

The values must be equal. Numbers, and strings that read as numbers, are compared numerically; other strings are compared exactly, including case. Symbol forms: = and ==.

Rules
field.currency equals "CLP"
field.total equals field.amount_due
Document
{ "currency": "clp", "total": "1.190,50", "amount_due": 1190.5 }
Result
{
  "valid": false,
  "errors": [
    { "field": "currency", "code": "VALIDATION_ERROR", "message": "El campo 'currency' debe ser igual a 'CLP', pero es 'clp'" }
  ]
}

notEquals

The values must differ, with the same comparison as equals. Symbol form: !=.

Rules
field.buyer_tax_id notEquals field.seller_tax_id
Document
{ "buyer_tax_id": "76.123.456-7", "seller_tax_id": "76.123.456-7" }
Result
{
  "valid": false,
  "errors": [
    { "field": "buyer_tax_id", "code": "VALIDATION_ERROR", "message": "El campo 'buyer_tax_id' no debe ser igual a '76.123.456-7'" }
  ]
}

greaterThan

The value must be a number strictly greater than the parameter. Symbol form: >. A value or parameter that is not a number fails with INVALID_TYPE; a comparison that does not hold fails with INVALID_RANGE.

Rules
field.total greaterThan 0
field.quantity > 0
Document
{ "total": 0, "quantity": "abc" }
Result
{
  "valid": false,
  "errors": [
    { "field": "total", "code": "INVALID_RANGE", "message": "El campo 'total' debe ser mayor que 0" },
    { "field": "quantity", "code": "INVALID_TYPE", "message": "El campo 'quantity' debe ser un número y su parámetro también: 'abc' (string) y '0' (number)" }
  ]
}

lessThan

The value must be a number strictly less than the parameter. Symbol form: <.

Rules
field.discount lessThan field.subtotal
Document
{ "discount": 500, "subtotal": 400 }
Result
{
  "valid": false,
  "errors": [
    { "field": "discount", "code": "INVALID_RANGE", "message": "El campo 'discount' debe ser menor que 400" }
  ]
}

greaterOrEqual / greaterThanOrEqual

The value must be a number greater than or equal to the parameter. Both names are the same operator. Symbol form: >=.

Rules
field.balance greaterOrEqual 0
field.paid greaterThanOrEqual field.total
Document
{ "balance": -10, "paid": 1000, "total": 1000 }
Result
{
  "valid": false,
  "errors": [
    { "field": "balance", "code": "INVALID_RANGE", "message": "El campo 'balance' debe ser mayor o igual a 0" }
  ]
}

lessOrEqual / lessThanOrEqual

The value must be a number less than or equal to the parameter. Both names are the same operator. Symbol form: <=.

Rules
field.used_credit lessOrEqual field.credit_limit
field.tax_rate lessThanOrEqual 0.19
Document
{ "used_credit": 1200000, "credit_limit": 1000000, "tax_rate": 0.19 }
Result
{
  "valid": false,
  "errors": [
    { "field": "used_credit", "code": "INVALID_RANGE", "message": "El campo 'used_credit' debe ser menor o igual a 1000000" }
  ]
}

between

The value must be a number within the two limits, both included: between(min, max).

Rules
field.tax_rate between(0, 0.19)
Document
{ "tax_rate": 0.21 }
Result
{
  "valid": false,
  "errors": [
    { "field": "tax_rate", "code": "INVALID_RANGE", "message": "El campo 'tax_rate' debe estar entre 0 y 0.19" }
  ]
}

range

Same check as between, reported with rule range: range(min, max).

Rules
field.month range(1, 12)
Document
{ "month": 13 }
Result
{
  "valid": false,
  "errors": [
    { "field": "month", "rule": "range", "code": "INVALID_RANGE", "message": "El campo 'month' debe estar entre 1 y 12" }
  ]
}

equalsWithPrecision

The value must equal the expected number when both are rounded to precision decimals: equalsWithPrecision(expected, precision). precision defaults to 2. Use it to compare totals that carry rounding, such as a sum of line items against the document total. In practice, two numbers pass when they differ by less than half a unit of the last decimal (0.005 with precision 2).

Rules
field.subtotal equalsWithPrecision(field.items_total, 2)
field.total equalsWithPrecision(1000, 0)
Document
{ "subtotal": 1000.004, "items_total": 1000, "total": 1000.6 }
Result
{
  "valid": false,
  "errors": [
    { "field": "total", "code": "VALIDATION_ERROR", "message": "El campo 'total' debe ser igual a '1000' con 0 decimales de precisión, pero es '1000.6'" }
  ]
}

Text and format

length

A string or list must have exactly this length.

Rules
field.country_code length 2
Document
{ "country_code": "CHL" }
Result
{
  "valid": false,
  "errors": [
    { "field": "country_code", "code": "INVALID_LENGTH", "message": "El campo 'country_code' debe tener longitud 2, actual: 3" }
  ]
}

minLength

A string or list must have at least this length.

Rules
field.holder_name minLength 2
field.items minLength 1
Document
{ "holder_name": "A", "items": [] }
Result
{
  "valid": false,
  "errors": [
    { "field": "holder_name", "code": "INVALID_LENGTH", "message": "El campo 'holder_name' debe tener al menos 2 caracteres, actual: 1" },
    { "field": "items", "code": "INVALID_LENGTH", "message": "El campo 'items' debe tener al menos 1 caracteres, actual: 0" }
  ]
}

maxLength

A string or list must have at most this length.

Rules
field.description maxLength 10
Document
{ "description": "Consulting services" }
Result
{
  "valid": false,
  "errors": [
    { "field": "description", "code": "INVALID_LENGTH", "message": "El campo 'description' debe tener máximo 10 caracteres, actual: 19" }
  ]
}

pattern

A string must match a regular expression, written as /regex/flags or as a quoted string. The expression is not anchored: add ^ and $ to match the whole value.

Rules
field.tax_id pattern /^\d{1,2}\.\d{3}\.\d{3}-[\dkK]$/
field.folio pattern "^F-\d+$"
Document
{ "tax_id": "76.123.456-7", "folio": "INV-1" }
Result
{
  "valid": false,
  "errors": [
    { "field": "folio", "code": "INVALID_PATTERN", "message": "El campo 'folio' no cumple con el patrón requerido" }
  ]
}

Patterns run on RE2, which takes time proportional to the length of the value, so no expression can stall validation. RE2 supports most of JavaScript's syntax, with these differences:

  • Not supported (rejected): backreferences (\1, \k<name>), lookahead and lookbehind ((?=…), (?!…), (?<=…), (?<!…)), [], [^], [\b], \cX, \e and \C, and a lone surrogate (\uD83D without its pair).
  • Different meaning: in JavaScript these were literal characters; in RE2 they mean something. \A and \z are the start and end of the text, \a is the bell character, \Q…\E quotes literal text, \p{…} is a Unicode class, and \u{…} is a code point.
  • Flags: i, m and s work as in JavaScript, with one difference: with m, only \n ends a line, so ^a$ with m does not match a followed by \r\n. Without s, . matches every character except \n, including \r, U+2028 and U+2029. Unicode is always on, so u is implied: . matches one character, even an emoji. g and y have no effect.
  • Whitespace: \s matches only ASCII whitespace; it does not match a non-breaking space (U+00A0), \v or U+FEFF.
  • Escapes and emoji: \uXXXX and \u{X} work. An emoji works written literally (😀), as \u{1F600}, or as its escaped surrogate pair \uD83D\uDE00, also inside a class that lists emoji one by one ([😀😃]). A lone surrogate, including \u{D800}–\u{DFFF}, is rejected. A range of emoji in a class, such as [😀-🙏], only works in a regular expression literal with the u flag (/^[😀-🙏]$/u); a quoted pattern has no flags, so it cannot express one.

Patterns also have size limits. A pattern can have at most 500 characters, and it can compile to at most 300 instructions. Ordinary patterns compile to 8–70; counted repetitions are what grow it, so .{1000} repeated is rejected as too complex, and so is ^.{1,255}$. To limit the length of a value, use maxLength instead.

Saving or testing rules through the API or MCP fails with 400, with line and column, if a pattern is not valid, uses unsupported syntax, or exceeds a limit. Rules saved before keep running; a pattern that cannot be used, or a value longer than 10,000 characters, reports RULE_EXECUTION_ERROR instead.

format

A string must have a known format: email, date (YYYY-MM-DD, a real calendar day), time (HH:MM:SS), datetime or date-time (ISO 8601), url (http or https), phone or uuid. An unknown format fails with VALIDATION_ERROR.

Rules
field.contact_email format email
field.issue_date format date
field.website format url
Document
{ "contact_email": "billing@acme.example", "issue_date": "2024-02-30", "website": "ftp://acme.example" }
Result
{
  "valid": false,
  "errors": [
    { "field": "issue_date", "code": "INVALID_FORMAT", "message": "El campo 'issue_date' debe ser una fecha válida" },
    { "field": "website", "code": "INVALID_FORMAT", "message": "El campo 'website' debe tener formato url" }
  ]
}

startsWith

A string must start with the given text (case-sensitive).

Rules
field.folio startsWith "F-"
Document
{ "folio": "B-1001" }
Result
{
  "valid": false,
  "errors": [
    { "field": "folio", "code": "VALIDATION_ERROR", "message": "El campo 'folio' debe empezar con 'F-'" }
  ]
}

endsWith

A string must end with the given text (case-sensitive).

Rules
field.file_name endsWith ".pdf"
Document
{ "file_name": "invoice.docx" }
Result
{
  "valid": false,
  "errors": [
    { "field": "file_name", "code": "VALIDATION_ERROR", "message": "El campo 'file_name' debe terminar con '.pdf'" }
  ]
}

includes

A string must contain the given text (case-sensitive).

Rules
field.supplier_name includes "SpA"
Document
{ "supplier_name": "Acme Ltda" }
Result
{
  "valid": false,
  "errors": [
    { "field": "supplier_name", "code": "VALIDATION_ERROR", "message": "El campo 'supplier_name' debe contener 'SpA'" }
  ]
}

Lists of values

contains

A list must contain the given element, or a string must contain the given text. List elements are compared exactly: 3 does not match "3".

Rules
field.tags contains "urgent"
field.payment_terms contains "30"
Document
{ "tags": ["paid", "export"], "payment_terms": "30 days" }
Result
{
  "valid": false,
  "errors": [
    { "field": "tags", "code": "VALIDATION_ERROR", "message": "El campo 'tags' debe contener 'urgent'" }
  ]
}

in

The value must be one of the listed values: in("CLP", "USD"). Values are compared exactly, without number normalization: "2" is not in in(1, 2, 3).

Rules
field.currency in("CLP", "USD")
Document
{ "currency": "EUR" }
Result
{
  "valid": false,
  "errors": [
    { "field": "currency", "code": "INVALID_ENUM", "message": "El campo 'currency' debe ser uno de: CLP, USD" }
  ]
}

notIn

The value must not be one of the listed values, compared exactly.

Rules
field.status notIn("void", "draft")
Document
{ "status": "void" }
Result
{
  "valid": false,
  "errors": [
    { "field": "status", "code": "VALIDATION_ERROR", "message": "El campo 'status' no puede ser uno de: void, draft" }
  ]
}

Dates

Date operators compare calendar days. They accept YYYY-MM-DD, ISO 8601 date-times (converted to their UTC day), and DD/MM/YYYY or DD-MM-YYYY. The parameter can be a literal date or a field reference. A value that is not a valid date fails with INVALID_TYPE; a comparison that does not hold fails with VALIDATION_ERROR. See dates.

dateEquals

Both dates must be the same day.

Rules
field.delivery_date dateEquals field.issue_date
Document
{ "delivery_date": "15/03/2024", "issue_date": "2024-03-15" }
Result
{ "valid": true, "errors": [] }

dateNotEquals

The dates must be different days.

Rules
field.due_date dateNotEquals field.issue_date
Document
{ "due_date": "2024-03-15", "issue_date": "15-03-2024" }
Result
{
  "valid": false,
  "errors": [
    { "field": "due_date", "code": "VALIDATION_ERROR", "message": "El campo 'due_date' no debe ser igual a '2024-03-15'" }
  ]
}

dateAfter

The date must be strictly after the parameter.

Rules
field.due_date dateAfter field.issue_date
Document
{ "due_date": "2024-03-01", "issue_date": "2024-03-15" }
Result
{
  "valid": false,
  "errors": [
    { "field": "due_date", "code": "VALIDATION_ERROR", "message": "El campo 'due_date' debe ser posterior a '2024-03-15'" }
  ]
}

dateBefore

The date must be strictly before the parameter.

Rules
field.issue_date dateBefore "2030-01-01"
Document
{ "issue_date": "2031-06-30" }
Result
{
  "valid": false,
  "errors": [
    { "field": "issue_date", "code": "VALIDATION_ERROR", "message": "El campo 'issue_date' debe ser anterior a '2030-01-01'" }
  ]
}

dateAfterOrEqual

The date must be the same day as the parameter or later.

Rules
field.period_end dateAfterOrEqual field.period_start
Document
{ "period_start": "2024-05-01", "period_end": "30/04/2024" }
Result
{
  "valid": false,
  "errors": [
    { "field": "period_end", "code": "VALIDATION_ERROR", "message": "El campo 'period_end' debe ser posterior o igual a '2024-05-01'" }
  ]
}

dateBeforeOrEqual

The date must be the same day as the parameter or earlier.

Rules
field.issue_date dateBeforeOrEqual field.received_date
Document
{ "issue_date": "2024-05-02", "received_date": "2024-05-01" }
Result
{
  "valid": false,
  "errors": [
    { "field": "issue_date", "code": "VALIDATION_ERROR", "message": "El campo 'issue_date' debe ser anterior o igual a '2024-05-01'" }
  ]
}

dateBetween

The date must fall between two dates, both included: dateBetween(start, end).

Rules
field.transaction_date dateBetween(field.period_start, field.period_end)
Document
{ "transaction_date": "2024-07-01", "period_start": "2024-06-01", "period_end": "2024-06-30" }
Result
{
  "valid": false,
  "errors": [
    { "field": "transaction_date", "code": "VALIDATION_ERROR", "message": "El campo 'transaction_date' debe estar entre '2024-06-01' y '2024-06-30'" }
  ]
}

Calculated values

These operators compute a value and store it as calculated.<name>. They only work inside define; written as a standalone rule, such as field.items sum, they fail with the message "El operador 'sum' solo se puede usar dentro de define". See calculated values.

define

define <name> <expression> stores a value under calculated.<name>. The expression is a field, a literal, or one of the operations below. define itself never produces an issue unless the calculation fails.

Rules
define declared field.total
define minimum 1000
calculated.declared greaterOrEqual calculated.minimum
Document
{ "total": 800 }
Result
{
  "valid": false,
  "errors": [
    { "field": "calculated.declared", "code": "INVALID_RANGE", "message": "El campo 'calculated.declared' debe ser mayor o igual a 1000" }
  ]
}

sum

Adds every number from its operands. An operand can be a field, a [*] wildcard over a list, a calculated value or a literal. An empty list adds up to 0; a value that is not a number makes define fail with VALIDATION_ERROR.

Rules
define itemsTotal sum field.items[*].amount
calculated.itemsTotal equals field.subtotal
Document
{ "subtotal": 2500000, "items": [{ "amount": "1.000.000" }, { "amount": 1500000 }] }
Result
{ "valid": true, "errors": [] }

count

Counts the elements of a list, written as field.items[*] or field.items. Add where <property> <comparison> <number> to count only the elements that match, with >, <, >=, <=, =, == or !=.

Rules
define lines count field.items[*]
define bigLines count field.items[*] where amount > 100000
calculated.lines equals 3
calculated.bigLines lessOrEqual 1
Document
{ "items": [{ "amount": 50000 }, { "amount": 200000 }, { "amount": 300000 }] }
Result
{
  "valid": false,
  "errors": [
    { "field": "calculated.bigLines", "code": "INVALID_RANGE", "message": "El campo 'calculated.bigLines' debe ser menor o igual a 1" }
  ]
}

average

The average of every number from its operands; 0 when there are none.

Rules
define averagePrice average field.items[*].unit_price
calculated.averagePrice lessThan 1000
Document
{ "items": [{ "unit_price": 900 }, { "unit_price": 1500 }] }
Result
{
  "valid": false,
  "errors": [
    { "field": "calculated.averagePrice", "code": "INVALID_RANGE", "message": "El campo 'calculated.averagePrice' debe ser menor que 1000" }
  ]
}

min

The smallest number from its operands.

Rules
define lowestQuantity min field.items[*].quantity
calculated.lowestQuantity greaterThan 0
Document
{ "items": [{ "quantity": 3 }, { "quantity": 0 }] }
Result
{
  "valid": false,
  "errors": [
    { "field": "calculated.lowestQuantity", "code": "INVALID_RANGE", "message": "El campo 'calculated.lowestQuantity' debe ser mayor que 0" }
  ]
}

max

The largest number from its operands.

Rules
define largestCharge max field.transactions[*].amount
calculated.largestCharge lessOrEqual field.credit_limit
Document
{ "credit_limit": 500000, "transactions": [{ "amount": 120000 }, { "amount": 480000 }] }
Result
{ "valid": true, "errors": [] }

subtract

Subtracts every following operand from the first one. Each operand is added up first, so a [*] wildcard subtracts the sum of the list. It needs at least two operands.

Rules
define pending subtract field.total field.payments[*].amount
calculated.pending equals field.balance_due
Document
{ "total": 1000, "payments": [{ "amount": 300 }, { "amount": 200 }], "balance_due": 400 }
Result
{
  "valid": false,
  "errors": [
    { "field": "calculated.pending", "code": "VALIDATION_ERROR", "message": "El campo 'calculated.pending' debe ser igual a '400', pero es '500'" }
  ]
}

define also accepts two operations that are not standalone operators: add, the same as sum, written as add(field.a, field.b), and concat, which joins values into text. See the validation guide.

On this page