# Kibble installation contract: section 13 of 29 (guide version 3)

> This is **one section only**, not the whole contract. Complete document: https://www.usekibble.dev/AI/guide.md

## 13. Forms {#forms}

All form tracking needs a form marked with `data-kibble-form`. Field values are never read. A form submit is an **attempt**; only `form_accepted` is a confirmed success.

```html
<form data-kibble-form="contact" data-kibble-form-type="contact">   <!-- contact|booking|checkout|newsletter|other -->
  <input name="email" data-kibble-field="email">
  <p data-kibble-error="email" data-kibble-error-code="format"> ... </p>   <!-- required|format|invalid|server -->
</form>
```

### What is recorded

- `form_start`: the first focus or input inside the form on a page view.
- `form_submit`: the form's `submit` event. A programmatic `form.submit()` fires no event and records nothing; use `form.requestSubmit()`. Booking forms add their criteria here ([Booking engines and booking widgets](https://www.usekibble.dev/AI#booking)).
- `form_error`: see below.
- `form_accepted`: only from the acceptance wiring below.

### Validation errors

- Native constraint validation is captured automatically through the browser's `invalid` event: field name and code `required`, `format` or `invalid`.
- For JavaScript validation, render an element inside the form with `data-kibble-error` and `data-kibble-error-code` (`required`, `format`, `invalid` or `server`). Any other code is dropped. Never put the entered value or the error message text in attributes.
- Field names come from `data-kibble-field` or the input `name`, lowercased, letters, digits and `_ . -` only. Names that contain `@` or six or more digits are dropped.
- If the same validation is reported both natively and through `data-kibble-error` markup, it is counted twice. Pick one for each field.

### Acceptance: confirmed success

Call `formAccepted` only in the branch where the server or application accepted the submission. Never in `onSubmit`, never on a validation failure or a 2xx that reports an error.

```js
// Form success: only in the branch where the server/application accepted the submission.
fetch('/api/contact', { method: 'POST', body: data })
  .then(async (r) => {
    const body = await r.json().catch(() => null);
    // HTTP success AND the application's own success flag (use whatever field this site's API really returns)
    if (r.ok && body && body.success === true) kibble('formAccepted', 'contact');   // { ref } only for a stable server reference
    // never in onSubmit, never on a validation failure or a 2xx that reports an error
  });
```

- Pass `{ ref }` only when the site has a stable submission reference. It is hashed in the browser. See [Outcomes](https://www.usekibble.dev/AI#outcomes) for how repeat submissions without a reference are counted.
- **Contact Form 7** is automatic: the SDK reports `form_accepted` when mail is sent. Do not add your own hook; it would count twice. The form id is the `data-kibble-form` of the form, otherwise `cf7_` plus the form number.
- **WPForms** (AJAX) and **Gravity Forms** (AJAX confirmation) are reported by the WordPress plugin helper as `wpforms_<id>` and `gf_<id>`. To get starts and errors under the same id, mark the form `data-kibble-form="wpforms_<id>"` or `data-kibble-form="gf_<id>"`. The plugin does not add markup. See [WordPress](https://www.usekibble.dev/AI#wordpress).
- A form accepted this way counts as a lead outcome.

END OF SECTION forms
