The architectural decision to make Form.io forms operate as infrastructure shapes how validation and conditional logic work: rules are defined once in the form schema, then executed consistently wherever the form runs. The renderer handles client-side validation for immediate user feedback. The API server re-executes the same rules on submission to prevent bypassing. You write the logic once; it runs in both places.
This matters because validation is where most form implementations fail. Client-side validation gets bypassed. Server-side validation drifts from client rules. Conditional logic that works in the browser breaks when data hits the API. Form.io solves this by making the form schema the single source of truth for both validation rules and conditional display logic.
The Two Layers of Validation
Form.io validation operates at two distinct layers, and understanding this separation prevents common implementation mistakes.
The renderer layer (client-side) validates as users interact with the form. Required fields show errors when left empty. Pattern matching catches malformed email addresses. Min/max constraints prevent out-of-range values. This happens in the browser before any data is submitted, providing immediate feedback that improves user experience.
The API layer (server-side) re-validates when submissions arrive. Even if client-side validation passes, the server will reject submissions that violate the schema’s validation rules. This design assumes that client-side validation can be bypassed, whether through browser manipulation, API calls that skip the renderer entirely, or bugs in the front-end code.
The Form Evaluations documentation explains how the renderer executes JavaScript within a sandboxed environment. The same evaluation context applies to both validation and conditional logic, which is why these features share the same variables: data, row, component, instance, value, and others.
Built-in Validation Rules
Every form component in Form.io includes standard validation options configured through the Validation tab in the form builder. These cover most common requirements without writing any code.
Required marks a field as mandatory. The form cannot be submitted until the field has a value. When a field is conditionally hidden, the required constraint is automatically suspended because invisible fields should not block submission.
Minimum and Maximum Length constrain text input. A text field with minLength: 10 and maxLength: 500 will reject entries outside that range on both client and server.
Minimum and Maximum Value constrain numeric input. A number field with min: 0 and max: 100 prevents negative numbers and values over 100.
Regular Expression Pattern validates against a regex. Phone numbers, postal codes, custom identifiers with specific formats all work through pattern validation. The pattern is defined once and enforced everywhere.
Unique ensures no duplicate values exist in the submission database. This validation is server-side only because it requires querying existing submissions. The API documentation covers how uniqueness checks interact with submission queries.
These built-in rules handle straightforward requirements. Complex business logic requires custom validation.
Custom Validation with JavaScript
When built-in rules are insufficient, custom validation lets you write JavaScript that determines whether a field is valid. The validation code runs in a sandboxed environment with access to the entire form’s data.
The pattern is straightforward. Set the valid variable to true if validation passes, or to an error message string if it fails:
valid = (input === data.confirmEmail) ? true : 'Email addresses must match';
This example validates that a confirmation email field matches the primary email field. The input variable contains the current field’s value. The data object contains all form data, accessed by each field’s API property name.
The Logic & Conditions documentation lists all available variables in the evaluation context:
form: The complete form JSON schemasubmission: The complete submission objectdata: All submission data as key-value pairsrow: Contextual row data when inside DataGrid, EditGrid, or Container componentscomponent: The current component’s JSON definitioninstance: The current component instancevalue: The current field’s valueinput: Alias for value in validation contextsmoment`: The moment.js library for date operations_: Lodash utility libraryutils: Form.io utility functions
Custom validation executes on every field change by default. Use the “Validate On” setting to control timing: validate on change, on blur, or only on form submission.
JSON Logic as an Alternative to JavaScript
Form.io supports JSON Logic for validation and conditional rules. JSON Logic expresses logic as JSON objects rather than JavaScript code, which provides several advantages in certain contexts.
JSON Logic validation looks like this:
{
"if": [
{ "==": [{ "var": "data.country" }, "US"] },
{ "and": [
{ ">=": [{ "var": "input" }, 10000] },
{ "<=": [{ "var": "input" }, 99999] }
]},
true
]
}
This validates that a ZIP code is a 5-digit number, but only when the country is US. The var operator retrieves values from the data context using dot notation.
JSON Logic is useful when you need to store validation rules in a database, generate rules programmatically, or avoid JavaScript execution in environments with strict security policies. The formio.js examples demonstrate JSON Logic for both validation and conditional display.
The trade-off: JSON Logic is more verbose than JavaScript for complex rules. Use JavaScript for sophisticated validation; use JSON Logic when portability or security constraints require it.
Form Conditional Logic: Show and Hide Fields
Conditional logic in Form.io controls field visibility based on other fields’ values. A field can be shown or hidden depending on what the user has entered elsewhere in the form. This creates dynamic forms that adapt to user input without page reloads.
Simple conditions handle single-field dependencies through a point-and-click interface. Open any component’s settings, navigate to the Conditional tab, and configure:
- Whether to show or hide the field when conditions are met
- Which field triggers the condition
- What value triggers the condition
For example, a “Spouse Information” panel can be configured to show only when the “Marital Status” field equals “Married”. No code required.
Simple conditions support multiple triggers with AND/OR logic. Show a field when condition A is true AND condition B is true. Or show it when either condition is true. The comparison operators include equals, not equals, is empty, is not empty, less than, greater than, and variations with “or equal to”.
When a field is hidden by conditional logic, two things happen automatically. First, the field is removed from the visible form. Second, the field’s value is cleared from the submission data by default. This prevents hidden fields from containing stale data. The “Clear Value When Hidden” setting controls this behavior if you need hidden fields to retain their values.
Advanced Conditional Logic
For conditions that depend on multiple fields, calculations, or complex comparisons, advanced conditions use JavaScript or JSON Logic.
JavaScript conditions set the show variable to true or false:
show = (data.income < 50000) && (data.dependents > 2);
This shows a field only when income is below 50,000 AND the user has more than two dependents. Any JavaScript expression that evaluates to a boolean works here.
A common pattern for conditional logic is showing different form sections based on a selection. A “Type of Request” radio button with options for “New Application”, “Renewal”, and “Amendment” can trigger three different sets of fields. Each subsequent panel uses a simple condition checking the radio button’s value.
The Premium and Custom Form Components documentation covers components that have special conditional behaviors, like the EditGrid which maintains row-level conditional logic independently.
The Logic System: Beyond Show/Hide
Form.io’s Logic feature extends conditional behavior beyond visibility. While conditions control whether a field appears, Logic controls what a field does when conditions are met.
Logic operates through triggers and actions. A trigger defines when the logic activates. An action defines what happens. Multiple actions can fire from a single trigger, and multiple logic rules can exist on a single field.
Trigger types include:
- Simple: Same UI as conditional show/hide
- JavaScript: Custom code returning true/false
- JSON Logic: JSON-based boolean expressions
- Event: Fires when a custom event is emitted
Action types include:
- Property: Change field settings like required, disabled, or label
- Value: Set or calculate the field’s value
- Merge Component Schema: Modify the field’s JSON definition
- Custom Action: Execute arbitrary JavaScript
A practical example: make a field required only when another field has a specific value. The trigger checks data.requestType === 'urgent'. The action sets property: validate.required to true. When the request type is urgent, the field becomes required. When it changes to something else, the requirement is removed.
This is more powerful than simple conditional logic because it changes field behavior, not just visibility. The Logic documentation covers the full range of actions.
Calculated Values
Calculated values populate a field based on other fields’ data. This is distinct from validation (which checks data) and conditions (which control visibility). Calculations produce data.
The pattern uses the value variable:
value = data.quantity * data.unitPrice;
This calculates a total from quantity and unit price fields. The calculated field updates whenever its dependencies change.
Calculations can reference any field in the form, perform date math using moment.js, use Lodash utilities for data manipulation, or call custom functions defined in your project’s Form Module settings.
A word of caution: calculated fields that depend on many other fields can impact form performance. Each dependency change triggers recalculation. For forms with extensive calculations, consider whether some computations should happen server-side after submission rather than in real-time during form completion.
Validation Timing and Performance
Form.io evaluates validation and conditional logic on every change by default. For simple forms, this is fine. For complex forms with many conditions and custom validations, this can degrade performance.
Several strategies address performance concerns:
Use Simple Conditions instead of JavaScript conditions whenever possible. The documentation explicitly recommends this. Simple conditions use optimized evaluation paths. JavaScript conditions require secure sandboxed execution, which adds overhead.
Set “Validate On” to blur or submit instead of change for fields with expensive validation logic. This reduces how often validation runs.
Minimize DOM manipulation in Logic actions. The “Merge Component Schema” action is powerful but causes component re-rendering. Use it sparingly.
Use the Redraw setting carefully. Components can be configured to redraw when any form value changes, but unnecessary redraws hurt performance.
The Offline Mode documentation covers additional considerations for forms that must work without network connectivity, where validation behavior interacts with data synchronization.
Secret Validations
Some validation rules should not be visible to end users. Password complexity requirements, business rule details, or validation logic that reveals system constraints might need to remain hidden.
Form.io’s “Secret Validation” setting hides custom validation JavaScript from non-administrative users. The validation still executes normally, but the code is not exposed in the form schema delivered to the browser. Users without administrative privileges see "customPrivate": true in the form JSON instead of the actual validation code.
This is not a security boundary for truly sensitive logic. The validation still runs client-side, which means a determined attacker could potentially reverse-engineer it. For validation that must remain completely hidden, implement it server-side using webhooks or custom API logic.
When Conditional Logic Breaks
Common failure modes and their causes:
Field not showing when it should: Check that you’re using the field’s API Property Name, not its label, in condition references. Property names are found in the API tab of component settings.
Validation not triggering: Ensure “Validate On” is set appropriately. If set to “submit”, validation won’t run until form submission.
Conditions work in builder but fail in production: JavaScript conditions require the secure evaluator. Some hosting environments with strict Content Security Policies may interfere with JavaScript execution.
Calculated values not updating: Check that dependent fields are using their Property Names correctly. Verify the calculation doesn’t have circular dependencies.
Performance degradation on complex forms: Profile which conditions and validations are most expensive. Convert JavaScript conditions to Simple conditions where possible.
The Form Revisions feature helps debug these issues by letting you compare form versions and identify when problems were introduced.
Integration with Workflows
Validation and conditional logic become more powerful when combined with Form.io’s action system. A form can trigger different integrations based on submission data. An email action can fire only when certain conditions are met. Data can route to different endpoints based on field values.
The Data Reporting feature uses MongoDB aggregation pipelines that respect the same field structure defined in your form schema. Conditional fields that are hidden and cleared do not appear in submission data, which keeps your data model clean.
For Multi-Page Form Wizards, conditional logic can control which pages appear. An entire wizard page can be conditionally shown or hidden based on selections made on previous pages.
The Schema is the Source of Truth
Form.io’s approach to validation and conditional logic reflects its architecture as schema-driven infrastructure. The form JSON contains all rules. The renderer and API server both read that schema. Neither layer invents its own validation.
This means validation rules can be updated without code deployments. Change the form schema through the Drag & Drop Form Builder, and both client and server immediately enforce the new rules. The Decoupled Form Updates feature makes this possible by separating form definitions from application code.
For teams building enterprise applications, this architecture means business rules encoded in form validation can be managed by business analysts with access to the form builder, not just developers with access to source code. The Teams & Permissions system controls who can modify these rules.
Related Resources
- Logic & Conditions Documentation: Complete reference for conditions, logic, validation, and calculated values
- Form Evaluations: Technical details on how JavaScript is evaluated in forms
- formio.js Examples: Live examples of JSON Logic conditions and validation
- JSON Logic Documentation: Reference for JSON Logic syntax
- Form.io GitHub Repository: Open source API server code
