> ## Documentation Index
> Fetch the complete documentation index at: https://docs.overflow.co/llms.txt
> Use this file to discover all available pages before exploring further.

# validate

> Custom synchronous validator that runs on every change, debounced.

`validate` is a custom synchronous callback that decides whether an element's current value is acceptable. It runs on every change (debounced), on blur (synchronous), and at submit time (synchronous).

Providing `validate` opts the element fully out of built-in validation. The callback owns the entire validity contract.

## Signature

```typescript theme={null}
type Validate<TValue> = (value: TValue) => string | null;
```

* Return `null` to signal the value is valid.
* Return any non-empty string to surface a `FieldError` with `code: 'custom'` and the returned string as `message`.

The value shape matches what appears on `onChange.value`:

| Element             | `TValue`                                        |
| ------------------- | ----------------------------------------------- |
| `email`             | `string \| null`                                |
| `fullName`          | `FullNameValue \| null`                         |
| `phone`             | `{ e164: string; countryCode: string } \| null` |
| `companyName`       | `string \| null`                                |
| `billingAddress`    | `BillingAddressValue \| null`                   |
| `shippingAddress`   | `ShippingAddressValue \| null`                  |
| `bank`              | `BankFieldValue \| null` (manual mode only)     |
| `text` (custom)     | `string \| null`                                |
| `number` (custom)   | `number \| null`                                |
| `select` (custom)   | `string \| null`                                |
| `checkbox` (custom) | `boolean`                                       |

`card` does not accept `validate`. `submitButton`, `applePay`, `googlePay`, and `checkout` also do not.

## Example

```javascript theme={null}
overflow.email({
  validate: (value) => {
    if (!value) return 'Email is required';
    if (!value.endsWith('@example.com')) return 'Use your @example.com address';
    return null;
  },
});
```

## Precedence: `validate` disables built-ins

When `validate` is supplied, the SDK skips all built-in checks for that element:

* The zod format schema (email format, address atom formats, and so on).
* Provider-level validity checks (for example, the phone element's E.164 check).
* The built-in `required` empty check.

The callback receives every value, including `null` for empty inputs. This is intentional: "I provided `validate`, so I own this."

To keep the built-in checks and layer business rules on top, leave `validate` unset and gate your business logic elsewhere (server-side, or inside your own `onChange` handler).

## Debouncing

The callback runs debounced. The default delay is 250 ms. Tune it per-instance:

```javascript theme={null}
new Overflow('live_pub_...', {
  validateDebounceMs: 400,
});
```

The debouncer is latest-wins: rapid keystrokes coalesce into one trailing-edge call. Pending callbacks flush synchronously on blur and on `submit()`, so error snapshots are current when the shopper tabs away or submits.

## Multi-field elements

For `billingAddress`, `shippingAddress`, and `bank`, the callback receives the whole-shape value:

```javascript theme={null}
overflow.billingAddress({
  validate: (value) => {
    if (!value) return 'Address is required';
    if (value.country === 'US' && value.state.length !== 2) {
      return 'Enter a two-letter state code';
    }
    return null;
  },
});
```

The returned message routes to the element-level error slot (rendered above the atoms). Per-atom custom validators are not exposed. Inspect whichever atoms you care about inside the whole-shape callback; use the built-in per-atom `required` and format checks for atom-keyed errors.

## Bank

`validate` on `bank` runs in manual mode only. In address-lookup mode, the SDK's own lookup owns validity timing and skips the callback.

## Async validation

`validateAsync` is reserved for a future release. Attempting to set it fails type-check today.

## `FieldError` from `validate`

```typescript theme={null}
{
  field: 'email',
  code: 'custom',
  message: 'Use your @example.com address',
}
```

Read errors from `onChange.errors` (live) or `onError.fieldErrors` (submit time).

## See also

* [`required`](/payment-elements/options/required)
* [`onChange` events](/payment-elements/events/on-change)
* [`onError` and `fieldErrors`](/payment-elements/events/on-error)
* [Handle validation errors](/payment-elements/guides/handle-validation-errors)
