Cross-field Validators

Validators that compare a field against another field, enforce uniqueness across repeater rows, or validate JSON-keyed response values.

These validators compare values across fields or across repeater rows. All cross-field validators must use the object form of the validator entry so their options are forwarded — passing a plain string loses the configuration.

// Correct — object form preserves options
{ "name": "isLessThanField", "options": { "fieldKey": "MAX_BUDGET" } }

// Wrong — plain string drops options silently
"isLessThanField"

isLessThanField

Key: isLessThanField

When to use: This field's numeric value must be strictly less than another field's value.

OptionTypeDescription
options.fieldKeystringKey of the field to compare against.
{
  "key": "NUMBER_OF_OCCUPANTS",
  "type": "input",
  "props": {
    "label": "Number of Occupants",
    "type": "number",
    "required": false,
    "defaultRequired": true
  },
  "validators": {
    "validation": [{ "name": "isLessThanField", "options": { "fieldKey": "NUMBER_OF_SEATS" } }]
  },
  "validation": {
    "messages": {
      "required": "Number of occupants is required",
      "isLessThanField": "Number of occupants cannot exceed the number of seats"
    }
  }
}

isGreaterThanField

Key: isGreaterThanField

When to use: This field's numeric value must be strictly greater than another field's value.

OptionTypeDescription
options.fieldKeystringKey of the field to compare against.
{
  "key": "MAX_BUDGET",
  "type": "input",
  "props": {
    "label": "Maximum Budget",
    "type": "number",
    "required": false,
    "defaultRequired": true
  },
  "validators": {
    "validation": [{ "name": "isGreaterThanField", "options": { "fieldKey": "MIN_BUDGET" } }]
  },
  "validation": {
    "messages": {
      "required": "Maximum budget is required",
      "isGreaterThanField": "Maximum budget must be greater than minimum budget"
    }
  }
}

uniqueField

Key: uniqueField

When to use: Reject a value that duplicates the value of one or more other top-level fields. Common for "applicant email must differ from contact email" or "primary phone must differ from secondary phone".

OptionTypeDescription
options.fieldKeysobjectMap keyed by the field that owns the validator. Each entry lists the other field keys to compare against and the message text.
{
  "key": "SECONDARY_PHONE",
  "type": "customphonenumber",
  "props": { "label": "Secondary Phone" },
  "validators": {
    "validation": [
      {
        "name": "uniqueField",
        "options": {
          "fieldKeys": {
            "SECONDARY_PHONE": {
              "fields": ["PRIMARY_PHONE"],
              "message": "Secondary phone must differ from primary phone"
            }
          }
        }
      }
    ]
  },
  "validation": {
    "messages": {
      "uniqueField": "Secondary phone must differ from primary phone"
    }
  }
}

Common mistakes:

  • Pointing fields at a key in a different repeater iteration — uniqueField compares against top-level form values only. For cross-row uniqueness use repeaterUniqueField or uniqueRepeaterValues.

jsonKeyValueIsEqualTo

Key: jsonKeyValueIsEqualTo

When to use: When the field value is an object (e.g. a fetch response payload), enforce that one of its keys equals a known value. Common after a customgenericdatafetch call: "the returned status must be ACTIVE".

OptionTypeDescription
options.keystringDot-path within the field value.
options.valueanyExpected match value.
{
  "key": "POLICY_LOOKUP",
  "type": "customgenericdatafetch",
  "props": { "label": "Insurance Policy Number" },
  "validators": {
    "validation": [
      {
        "name": "jsonKeyValueIsEqualTo",
        "options": { "key": "policyStatus", "value": "RENEWABLE" }
      }
    ]
  },
  "validation": {
    "messages": {
      "jsonKeyValueIsEqualTo": "This policy cannot be renewed"
    }
  }
}

jsonKeyValueIsNotEqualTo

Key: jsonKeyValueIsNotEqualTo

When to use: Inverse of jsonKeyValueIsEqualTo. Enforce that a key in the field's object value is not equal to a forbidden value.

OptionTypeDescription
options.keystringDot-path within the field value.
options.valueanyForbidden value.
{
  "key": "APPLICATION_LOOKUP",
  "type": "customgenericdatafetch",
  "props": { "label": "Existing Application" },
  "validators": {
    "validation": [
      { "name": "jsonKeyValueIsNotEqualTo", "options": { "key": "state", "value": "REJECTED" } }
    ]
  },
  "validation": {
    "messages": {
      "jsonKeyValueIsNotEqualTo": "Cannot link to a rejected application"
    }
  }
}

uniqueRepeaterValues

Key: uniqueRepeaterValues

When to use: Attach to the repeater field itself (not a leaf field inside the row). Rejects the form when two rows duplicate the same value on a single property, or the same combination of values on multiple properties.

OptionTypeDescription
options.propKeysarrayEach entry is either a string (single-property uniqueness) or an array of strings (combination uniqueness).
{
  "key": "BENEFICIARIES",
  "type": "customrepeater",
  "props": { "label": "Beneficiaries" },
  "validators": {
    "validation": [
      {
        "name": "uniqueRepeaterValues",
        "options": {
          "propKeys": ["NATIONAL_ID", ["FIRST_NAME", "LAST_NAME", "DATE_OF_BIRTH"]]
        }
      }
    ]
  },
  "validation": {
    "messages": {
      "uniqueRepeaterValues": "Each beneficiary must have a unique National ID, and the same person cannot be listed twice"
    }
  },
  "fieldArray": { "fieldGroup": [] }
}

Common mistakes:

  • Attaching at a leaf field instead of the repeater — control.value won't be an array and the validator never fires.
  • Forgetting to wrap multi-property keys in an inner array — ["firstName", "lastName"] at the top level enforces each property independently, not the combination.

repeaterUniqueField

Key: repeaterUniqueField

When to use: Per-row uniqueness inside a repeater. Attach to a leaf field inside the row template. Rejects the form if the same key value appears in another row.

No options — the validator uses the field's parent context to find the repeater and compare across siblings.

{
  "key": "MEMBER_EMAIL",
  "type": "input",
  "props": { "label": "Member Email", "required": false, "defaultRequired": true, "type": "email" },
  "validators": { "validation": ["email", "repeaterUniqueField"] },
  "validation": {
    "messages": {
      "required": "Email is required",
      "repeaterUniqueField": "Each member must have a unique email"
    }
  }
}

Common mistakes:

  • Confusing repeaterUniqueField (leaf field, no options) with uniqueRepeaterValues (repeater root, uses propKeys).
  • Adding either validator to a non-repeater field — both are no-ops outside repeater context.

compareDateFields

Key: compareDateFields

When to use: Enforce relative date constraints between pairs of date fields (e.g. issue date before expiry date). Place on the root sections container, not on individual date fields.

OptionTypeDescription
options.fieldKeysobjectMap of source field keys to comparison rules. Put all date-pair rules here.
options.fieldKeys[key][].comparatorstring"before", "after", "beforeOrEquals", "afterOrEquals", "equals". Fires when the assertion is FALSE.
options.fieldKeys[key][].fieldKeystringTarget date field key.
options.fieldKeys[key][].messagestringError message when the constraint is violated.
options.delimeterstringDate string delimiter. Must match the date picker format. Default "/".
{
  "id": "SERVICE_FORM",
  "type": "sections",
  "validators": {
    "validation": [
      {
        "name": "compareDateFields",
        "options": {
          "fieldKeys": {
            "ISSUE_DATE": [
              {
                "comparator": "before",
                "fieldKey": "EXPIRY_DATE",
                "message": "The Expiry Date cannot be before the Issue Date"
              }
            ]
          }
        }
      }
    ]
  },
  "fieldGroup": []
}

Common mistakes:

  • Placing on an individual date field instead of the root sections container.
  • Adding as a plain string — it must use the object form with options.
  • Confusing the direction — "comparator": "before" on source A targeting B asserts "A is before B"; the error fires when A is NOT before B.

On this page