---
title: "Errors"
description: "Render field, form-level, and section errors, add server errors, and make error messages accessible."
canonical_url: "https://notformdocs.vercel.app/docs/guides/errors"
---

> Index of every page on this site: [llms.txt](https://notformdocs.vercel.app/llms.txt)

All errors are **issues**: an object with a `message` and an optional `path`.

```ts-type
{ message: 'Enter a valid email address.', path: ['email'] }
```

- `path` is an array of segments. Segments are property names and array indices: `['contacts', 0, 'email']`. Depending on the validator, a segment may be a string, a number, or an object with a `key`.
- An issue **without** a path is a form-level issue.

`form.errors` is the full list. Each field exposes the issues at its own exact path as `field.errors`.

## Show a field's error

`FieldError` renders the first message and nothing when there is no error.

```vue
<template>
  <Field
    v-slot="{ field }"
    :field="form.field('email')"
  >
    <label :for="field.path">Email</label>

    <input
      v-bind="field.events"
      :id="field.path"
      v-model="field.model"
      type="email"
    >

    <FieldError :errors="field" />
  </Field>
</template>
```

Style it, change its element, or use a slot. See [FieldError](https://notformdocs.vercel.app/docs/components/field-error).

```vue
<template>
  <FieldError
    :errors="field"
    as="p"
    class="text-red-600"
    role="alert"
  />
</template>
```

## Show every error

`FieldError` shows only the first message. To list all of them, render `field.errors`.

```vue
<template>
  <ul v-if="field.errors.length > 0">
    <li
      v-for="(issue, index) in field.errors"
      :key="index"
    >
      {{ issue.message }}
    </li>
  </ul>
</template>
```

## Show form-level errors

Read `form.errors` and keep the issues without a path.

```vue
<script setup lang="ts">
import { computed } from 'vue'

const formErrors = computed(() => {
  return form.errors.filter((issue) => {
    return !issue.path?.length
  })
})
</script>

<template>
  <p
    v-for="(issue, index) in formErrors"
    :key="index"
    role="alert"
  >
    {{ issue.message }}
  </p>
</template>
```

You can also pass an issue array straight to `FieldError`: `<FieldError :errors="formErrors" as="p" role="alert" />`.

## Show errors for a section

A field shows only issues at its exact path. An issue on `['address']` does not appear on the `address.city` field. To summarize a whole section, filter by the first segment.

```ts
function keyOf(segment: PropertyKey | { key: PropertyKey }) {
  return typeof segment === 'object' ? segment.key : segment
}

function sectionErrors(section: string) {
  return form.errors.filter((issue) => {
    return issue.path?.[0] !== undefined && String(keyOf(issue.path[0])) === section
  })
}
```

## Show an error summary

After a failed submit, `form.errors` holds every problem in the form. It is the natural source for a summary at the top of a long form.

```vue
<template>
  <div
    v-if="form.isSubmitted && form.errors.length > 0"
    role="alert"
  >
    <p>Fix {{ form.errors.length }} problem(s) to continue.</p>

    <ul>
      <li
        v-for="(issue, index) in form.errors"
        :key="index"
      >
        {{ issue.message }}
      </li>
    </ul>
  </div>
</template>
```

## Add errors yourself

`form.setError(issue)` adds an error, typically after a server response.

```ts
form.setError({ message: 'This email address is already in use.', path: ['email'] })
form.setError({ message: 'We could not reach the server.' }) // form-level
```

- With a `path`, it **replaces** the errors already at that exact path.
- Without a `path`, it **appends** a form-level error. Calling it twice adds two.

See [Server errors](https://notformdocs.vercel.app/docs/recipes/server-errors) for a complete submit flow.

## Start with errors

Pass `initialErrors` when a form should open with issues, for example after a server-side render.

```ts
const form = useForm({
  initialErrors: [{ message: 'Verify this address.', path: ['email'] }],
  initialValues: { email: 'jane@example.com' },
  schema,
})
```

`reset()` returns to the current initial errors. `reset(values, errors)` replaces them.

## When errors change

<table>
<thead>
  <tr>
    <th>
      Event
    </th>
    
    <th>
      Effect on errors
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      A field validates (blur, change, input, <code>
        field.validate()
      </code>
      
      )
    </td>
    
    <td>
      Replaces only the errors at that field's exact path.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        form.validate()
      </code>
    </td>
    
    <td>
      Replaces all errors with the schema's result.
    </td>
  </tr>
  
  <tr>
    <td>
      Submit
    </td>
    
    <td>
      Replaces all errors with the schema's result. A passing submit clears them before <code>
        onSubmit
      </code>
      
       runs.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        form.setError(issue)
      </code>
    </td>
    
    <td>
      Adds or replaces errors at one path, or appends a form-level error.
    </td>
  </tr>
  
  <tr>
    <td>
      An array mutation
    </td>
    
    <td>
      Errors stay attached to the item they belong to. Removed items lose their errors. See <a href="/docs/guides/array-fields">
        Array fields
      </a>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        form.reset()
      </code>
    </td>
    
    <td>
      Restores the initial errors.
    </td>
  </tr>
</tbody>
</table>

<warning>

Because a field validation replaces the errors at its path, a server error on `email` disappears the next time `email` validates and the schema reports no problem there. Form-level server errors stay until the next submit or reset. This is usually what you want, since editing a field makes its server error stale.

</warning>

## Accessible error markup

Connect each message to its control so assistive technology announces it.

```vue
<template>
  <Field
    v-slot="{ field }"
    :field="form.field('email')"
  >
    <label :for="field.path">Email</label>

    <input
      :id="field.path"
      v-model="field.model"
      v-bind="field.events"
      type="email"
      :aria-invalid="!field.isValid"
      :aria-describedby="field.isValid ? undefined : `${field.path}-error`"
    >

    <FieldError
      :id="`${field.path}-error`"
      :errors="field"
      role="alert"
    />
  </Field>
</template>
```

`FieldError` forwards `id`, `class`, `role`, and other attributes to the element it renders.

## Next steps

- [Submission and reset](https://notformdocs.vercel.app/docs/guides/submission-and-reset)
- [Server errors recipe](https://notformdocs.vercel.app/docs/recipes/server-errors)

## Sitemap

See the full [sitemap](https://notformdocs.vercel.app/sitemap.md) for all pages.
