Validation
Forma provides two levels of validation: schema-based validation from JSON Schema, and custom validation rules using FEEL expressions.
Schema Validation
Basic validation is handled automatically by the JSON Schema:
{
"schema": {
"properties": {
"email": { "type": "string", "format": "email" },
"age": { "type": "integer", "minimum": 0, "maximum": 120 },
"password": { "type": "string", "minLength": 8 }
},
"required": ["email", "age"]
}
}
Schema validation covers:
- Required fields
- Type checking (string, number, integer, boolean)
- String length (minLength, maxLength)
- Number ranges (minimum, maximum)
- Numeric precision (multipleOf)
- Format validation (email, date, uri)
- Enum values
- Array item count (minItems, maxItems)
- Nested constraints within array items
Custom Validation Rules
For validation logic that can't be expressed in JSON Schema, use the validations property:
{
"fields": {
"endDate": {
"label": "End Date",
"validations": [
{
"rule": "date(value) > date(startDate)",
"message": "End date must be after start date"
}
]
}
}
}
Array Validation
Arrays are validated for both item count and nested item constraints.
Item Count (minItems/maxItems)
Define item count constraints in the schema:
{
"schema": {
"properties": {
"participants": {
"type": "array",
"items": {
"type": "object",
"properties": { "name": { "type": "string" } }
},
"minItems": 2,
"maxItems": 10
}
}
}
}
Field definitions can override schema constraints:
{
"fields": {
"participants": {
"label": "Participants",
"type": "array",
"minItems": 1,
"maxItems": 5
}
}
}
When both are specified, fields.minItems/maxItems takes precedence over schema.minItems/maxItems.
Nested Item Validation
Constraints on array item properties are automatically validated:
{
"schema": {
"properties": {
"scores": {
"type": "array",
"items": {
"type": "object",
"properties": {
"studentName": { "type": "string", "minLength": 1 },
"score": { "type": "number", "minimum": 0, "maximum": 100 }
},
"required": ["studentName", "score"]
},
"minItems": 1
}
}
}
}
This validates:
- At least 1 item in the array
- Each item has
studentName(required, non-empty string) - Each item has
score(required, between 0-100)
Custom Validation in Array Items
Use itemFields for custom FEEL validation rules on array items:
{
"fields": {
"orders": {
"label": "Orders",
"type": "array",
"itemFields": {
"quantity": {
"label": "Quantity",
"validations": [
{
"rule": "value <= 100",
"message": "Cannot order more than 100 items"
}
]
}
}
}
}
}
Conditional Required in Array Items
Use requiredWhen for conditional requirements within array items:
{
"fields": {
"contacts": {
"type": "array",
"itemFields": {
"phone": {
"label": "Phone",
"requiredWhen": "item.contactMethod = \"phone\""
},
"email": {
"label": "Email",
"requiredWhen": "item.contactMethod = \"email\""
}
}
}
}
}
The item variable refers to the current array item, and itemIndex provides the 0-based index.
Validation Rule Structure
{
"validations": [
{
"rule": "...",
"message": "...",
"severity": "error"
}
]
}
| Property | Type | Required | Description |
|---|---|---|---|
rule | string | Yes | FEEL expression that must evaluate to true |
message | string | Yes | Error message shown when validation fails |
severity | string | No | "error" (default) or "warning" |
The value Variable
In validation rules, value refers to the current field's value:
{
"username": {
"validations": [
{
"rule": "string length(value) >= 3",
"message": "Username must be at least 3 characters"
}
]
}
}
Cross-Field Validation
Reference other fields to create cross-field validation:
{
"confirmPassword": {
"validations": [
{
"rule": "value = password",
"message": "Passwords must match"
}
]
}
}
Severity Levels
Error (default)
Errors prevent form submission:
{
"rule": "value >= 18",
"message": "You must be 18 or older",
"severity": "error"
}
Warning
Warnings are informational and don't block submission:
{
"rule": "value <= 65",
"message": "Please verify age for senior discount eligibility",
"severity": "warning"
}
Common Validation Patterns
String Validation
Minimum length:
{
"rule": "string length(value) >= 5",
"message": "Must be at least 5 characters"
}
Maximum length:
{
"rule": "string length(value) <= 100",
"message": "Cannot exceed 100 characters"
}
Pattern matching:
{
"rule": "matches(value, \"^[A-Z]{2}[0-9]{6}$\")",
"message": "Must be 2 letters followed by 6 digits (e.g., AB123456)"
}
Number Validation
Range:
{
"rule": "value >= 0 and value <= 100",
"message": "Must be between 0 and 100"
}
Must be positive:
{
"rule": "value > 0",
"message": "Must be a positive number"
}
Date Validation
Future date:
{
"rule": "date(value) > today()",
"message": "Date must be in the future"
}
Past date:
{
"rule": "date(value) < today()",
"message": "Date must be in the past"
}
Date range:
{
"rule": "date(value) >= date(startDate) and date(value) <= date(endDate)",
"message": "Date must be within the specified range"
}
Cross-Field Validation
Confirm match:
{
"rule": "value = originalField",
"message": "Values must match"
}
Date ordering:
{
"rule": "date(value) > date(startDate)",
"message": "End date must be after start date"
}
Conditional validation:
{
"rule": "if hasDiscount then value > 0 else true",
"message": "Discount amount is required when discount is enabled"
}
Using Computed Values
Reference computed values for complex validation:
{
"rule": "computed.total <= budget",
"message": "Order total exceeds available budget"
}
Multiple Validations
A field can have multiple validation rules:
{
"password": {
"validations": [
{
"rule": "string length(value) >= 8",
"message": "Password must be at least 8 characters"
},
{
"rule": "matches(value, \"[A-Z]\")",
"message": "Password must contain an uppercase letter"
},
{
"rule": "matches(value, \"[0-9]\")",
"message": "Password must contain a number"
}
]
}
}
Validation Order
- Schema validation runs first (type, format, required)
- Custom validation rules run when the field has a non-empty value, independently of type validation. All errors are collected together
- Hidden fields (visibleWhen = false) are not validated
Best Practices
-
Use schema validation first - Put type constraints, ranges, and formats in the schema
-
Keep messages user-friendly - Write clear, actionable error messages
-
Validate early - Add validation to catch errors as users type
-
Use warnings for soft validation - When you want to alert but not block
-
Test edge cases - Consider null values, empty strings, and boundary conditions