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

# Forms

> How the AI CMS form and submission model works, end to end.

A form in **AI CMS** is a structured document: an ordered list of **sections**, each
holding an ordered list of **fields**. When someone submits it, the API stores a
**submission** — a map of field names to values.

This page explains that model. The reference pages next to it list the individual
endpoints.

***

## How a form is built

```mermaid theme={null}
flowchart TD
    F["Form<br/><small>POST /forms/create</small>"]
    S1["Section 1<br/><small>title + fields</small>"]
    S2["Section 2<br/><small>title + fields</small>"]
    FLD["Field<br/><small>name, type, label, required</small>"]
    OPT["Options<br/><small>for select / checkbox types</small>"]
    REL["Relation<br/><small>links a field to a collection</small>"]
    SUB["Submission<br/><small>values keyed by field name</small>"]
    CAP["Captcha<br/><small>optional gate</small>"]

    F --> S1
    F --> S2
    S1 --> FLD
    FLD --> OPT
    FLD --> REL
    F --> CAP
    S1 --> SUB
    S2 --> SUB
```

A form is defined top-down, and a submission is defined by the field **names** you
chose. That is the one thing to get right: the `name` on a field is the key that will
appear in every submission for that form.

***

## Create a form

**Required:** `title`, `slug`, `sections`

`sections` must contain at least one section, and each section at least one field.
Section and field titles are multilingual maps — a language code to a string.

```json theme={null}
{
  "title": {
    "en": "Contact us",
    "es": "Contáctenos"
  },
  "slug": "contact-us",
  "description": { "en": "Get in touch with the team." },
  "submitButtonText": { "en": "Send" },
  "isActive": true,
  "formType": "public",
  "sections": [
    {
      "title": { "en": "Your details" },
      "fields": [
        {
          "name": "email",
          "type": "email",
          "label": { "en": "Email" },
          "required": true
        },
        {
          "name": "message",
          "type": "textarea",
          "label": { "en": "Message" },
          "required": true
        }
      ]
    }
  ]
}
```

***

## Field types

Every field needs `name`, `type`, `label`, and `required`. The `type` decides how the
value is validated on submit.

| **Type**                         | **Validation applied on submit**             |
| -------------------------------- | -------------------------------------------- |
| `email`                          | Must be a valid email address                |
| `tel`                            | International phone pattern                  |
| `number`                         | Must be numeric                              |
| `checkbox`, `switch`             | Must be a boolean                            |
| `multi-checkbox`, `multi-select` | Must be an array of values                   |
| `select`                         | Must be one of the field's `options[].value` |
| `text`, `textarea`, `date`       | Accepted without extra format checks         |

<Note>
  Only the types in the top half get format validation. A `text` field accepts any
  string, so put the constraint in your own UI if you need one.
</Note>

***

## Select and multi-select need options

A `select`, `multi-select`, or `multi-checkbox` field is only valid if it declares
`options`:

```json theme={null}
{
  "name": "topic",
  "type": "select",
  "label": { "en": "Topic" },
  "required": true,
  "options": [
    { "value": "sales", "label": { "en": "Sales" } },
    { "value": "support", "label": { "en": "Support" } }
  ]
}
```

Submissions are then validated against those `value`s — a submission with a topic that
is not in the list is rejected with a `422`.

***

## Public vs internal forms

`formType` decides where a form can be submitted from.

| **Value**  | **Behaviour**                                                              |
| ---------- | -------------------------------------------------------------------------- |
| `public`   | Reachable on the public submit route. This is the default.                 |
| `internal` | Only submittable from your backend via `POST /forms/{id}/internal-submit`. |

<Note>
  `internal` forms are for backend-to-backend use — they skip the public route
  entirely, which means they can carry fields you would not expose publicly.
</Note>

***

## Submitting a form

**Required:** `language`, `values`

`values` is a map keyed by the field `name`s you defined:

```json theme={null}
{
  "language": "en",
  "values": {
    "email": "nima@octaviatech.app",
    "message": "Hello, I have a question."
  }
}
```

If the form has `captcha.enabled` set to true, a `captchaToken` from the provider's
client widget is also required. See [Captcha](/api-reference/ai-cms/forms/get-captcha-config).

***

## Relations

A field can carry a `relation` that links it to a collection — for example a
`select` whose options come from your articles. Submissions store the linked IDs, and
you can manage them after the fact through the submission relation endpoints.

<Card title="Submission relations" icon="git-branch" href="/api-reference/ai-cms/forms/submission-relations" arrow="true">
  Connect, disconnect, and reorder related records on a submission
</Card>

***

## Where to go next

<CardGroup cols={2}>
  <Card title="Create a form" icon="plus" href="/api-reference/ai-cms/forms/create" arrow="true">
    The full create schema.
  </Card>

  <Card title="List submissions" icon="inbox" href="/api-reference/ai-cms/forms/submissions-get-all" arrow="true">
    Read every submission for the tenant.
  </Card>
</CardGroup>
