---
url: https://formhaus.dev/guide/validation.md
description: >-
  Validate form fields from JSON: required, pattern, min/max length and value,
  matchField for confirmations, and custom validator functions.
---

# Validation

Validation runs on Submit or Continue click. No validation on blur.

## Built-in rules

All rules are optional. Combine them freely.

```json
{
  "key": "password",
  "type": "password",
  "label": "Password",
  "validation": {
    "required": true,
    "minLength": 8,
    "minLengthMessage": "Password must be at least 8 characters",
    "pattern": "(?=.*[A-Z])(?=.*[0-9])",
    "patternMessage": "Must include a number and uppercase letter"
  }
}
```

| Rule | Type | What it checks | Default message |
|------|------|---------------|-----------------|
| `required` | `boolean \| string` | Not null, undefined, an empty string or an empty array. Checkbox and switch fields must be checked | "This field is required" |
| `minLength` | `number` | String length or selected-item count >= value | "Must be at least N characters/items" |
| `maxLength` | `number` | String length or selected-item count <= value | "Must be at most N characters/items" |
| `min` | `number` | Number >= value | "Must be at least N" |
| `max` | `number` | Number <= value | "Must be at most N" |
| `pattern` | `string` | Matches regex | "Invalid format" |
| `matchField` | `string` | Value equals another field's value | "Fields must match" |
| `validator` | `string` | Custom validator function returns null | (your message) |

`pattern` is not anchored: `[0-9]{5}` accepts `abc12345x`. Add `^` and `$` to match the whole value.

Length, range, pattern, and matching rules have a corresponding `...Message` field. For `required`, pass the message directly: `required: "Please enter your name"`.

## matchField

Useful for "confirm email" or "confirm password" fields. The values are compared the next time validation runs. `matchField` passes when the confirmation is empty, so keep `required` on it.

```json
[
  {
    "key": "email",
    "type": "email",
    "label": "Email",
    "validation": { "required": true }
  },
  {
    "key": "confirmEmail",
    "type": "email",
    "label": "Confirm email",
    "validation": {
      "required": true,
      "matchField": "email",
      "matchFieldMessage": "Email addresses must match"
    }
  }
]
```

## Custom validators

For synchronous business rules or format checks that cannot be expressed in JSON, register a validator function. Use [async step validation](/guide/async-validation) for server requests.

::: code-group

```vue [Vue]
<script setup>
import { FormRenderer } from '@formhaus/vue';

function validateFormat(value, allValues) {
  if (!value) return null;
  return isValidFormat(value) ? null : 'Invalid format';
}
</script>

<template>
  <FormRenderer
    :definition="definition"
    :validators="{ checkFormat: validateFormat }"
    @submit="onSubmit"
  />
</template>
```

```tsx [React]
<FormRenderer
  definition={definition}
  validators={{ checkFormat: validateFormat }}
  onSubmit={handleSubmit}
/>

function validateFormat(value: unknown, allValues: Record<string, unknown>) {
  if (!value) return null;
  return isValidFormat(value as string) ? null : 'Invalid format';
}
```

:::

Reference the validator by name in the definition:

```json
{
  "key": "code",
  "type": "text",
  "label": "Code",
  "validation": { "validator": "checkFormat" }
}
```

## When validation runs

* **On Continue.** Visible fields on the current step are validated.
* **On Submit.** Visible fields across all visible steps are validated, or all visible fields in a single-step form.
* **Async step validation.** If `onStepValidate` is provided, it runs after sync validation passes but before the step transition. See [Async Step Validation](/guide/async-validation).
* **Never on blur.** No errors while the user is still typing.
* **Hidden fields are skipped.** If a `show` condition hides a field, it's not validated.
* **Empty non-required fields are skipped.** `minLength`, `pattern`, etc. only run on non-empty values.
* **On the server.** Run [`validateSubmission()`](/guide/server-validation) on the posted values. The browser check does not verify value types or the `email` format.

## Next steps

* [Server-side Validation](/guide/server-validation): validate untrusted submissions
* [Error Handling](/guide/errors): display errors and handle loading states
* [Multi-Step Forms](/guide/steps): validate across multiple steps
* [Definition Reference](/api/definition): full TypeScript types for all validation rules
