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

# Form definitions

> Create and safely update form blocks, settings, and custom themes.

A form contains metadata plus a `definition` object:

```json theme={null}
{
  "id": "507f1f77bcf86cd799439011",
  "public_id": "contact-us",
  "workspace_id": "507f1f77bcf86cd799439012",
  "name": "Contact us",
  "status": "published",
  "is_closed": false,
  "definition": {
    "blocks": [],
    "settings": {},
    "theme": {}
  }
}
```

## Definition sections

| Section    | Behavior                                                                                                                |
| ---------- | ----------------------------------------------------------------------------------------------------------------------- |
| `blocks`   | Ordered questions and content blocks. Supplying this field replaces the complete block list.                            |
| `settings` | Form behavior such as navigation, branding, and closed state. Supplied keys are merged with existing settings.          |
| `theme`    | Colors, typography, layout, and other visual configuration. Supplied top-level keys are merged with the existing theme. |

Omitted sections remain unchanged. This makes it possible to update a form name or theme without resending its blocks.

## Create with blocks, settings, and a theme

Create a complete form by passing a `definition` to `POST /v1/forms`:

```json theme={null}
{
  "name": "Product feedback",
  "workspace_id": "507f1f77bcf86cd799439012",
  "type": "MULTI",
  "definition": {
    "blocks": [
      {
        "id": "rating",
        "type": "rating",
        "label": "How would you rate the product?",
        "description": "",
        "required": true,
        "properties": { "steps": 5 }
      },
      {
        "id": "comments",
        "type": "long-text",
        "label": "What could we improve?",
        "description": "",
        "required": false,
        "properties": { "maxChars": 2000 }
      },
      {
        "id": "ending",
        "type": "ending",
        "label": "Thank you",
        "description": "Your feedback has been recorded.",
        "required": false,
        "properties": { "buttonText": "Submit" }
      }
    ],
    "settings": {
      "progressBar": true,
      "navigation": true,
      "branding": true,
      "closed": false
    },
    "theme": {
      "font": "Inter",
      "corners": 0,
      "colors": {
        "primary": "#3969D9",
        "primaryForeground": "#FFFFFF"
      }
    }
  }
}
```

`type` is required. `MULTI` and `SINGLE` are available now. `CONVERSATIONAL` is reserved and returns `422 UNSUPPORTED_FORM_TYPE` until that renderer is released.
Do not send the old `single_page` field or write `definition.settings.singlePage`; the top-level `type` is authoritative.

## Block shape

Every block requires a stable `id` and supported `type`.

```json theme={null}
{
  "id": "customer-email",
  "type": "email",
  "label": "Email address",
  "description": "We will use this address to reply.",
  "required": true,
  "properties": {}
}
```

Block IDs are caller-supplied strings. Keep them stable across updates because submissions refer to fields by ID.

Blocks may also contain:

* `choices` for choice-based questions.
* `children` for nested groups.
* `part_of` for related display blocks.
* `row_id` for blocks placed in the same row.
* `properties` for type-specific settings.

The OpenAPI schema lists every supported block type.

## Manage blocks directly

Use the focused block endpoints when you do not want to replace the rest of the form definition:

| Operation                                    | Purpose                                           |
| -------------------------------------------- | ------------------------------------------------- |
| `GET /v1/forms/{formId}/blocks`              | List the ordered top-level block collection.      |
| `POST /v1/forms/{formId}/blocks`             | Add one block at an optional zero-based position. |
| `PUT /v1/forms/{formId}/blocks`              | Replace or reorder the complete collection.       |
| `GET /v1/forms/{formId}/blocks/{blockId}`    | Read one top-level block.                         |
| `PUT /v1/forms/{formId}/blocks/{blockId}`    | Fully replace and optionally move one block.      |
| `DELETE /v1/forms/{formId}/blocks/{blockId}` | Remove one block.                                 |

Add a question at position `1`:

```bash theme={null}
curl --request POST \
  "https://api.onlyform.com/v1/forms/507f1f77bcf86cd799439011/blocks" \
  --header "X-API-Key: $ONLYFORM_API_KEY" \
  --header 'If-Match: "m5v8w7g0"' \
  --header "Content-Type: application/json" \
  --data '{
    "position": 1,
    "block": {
      "id": "company",
      "type": "short-text",
      "label": "Company",
      "description": "",
      "required": false,
      "properties": { "maxChars": 120 }
    }
  }'
```

To reorder several blocks atomically, fetch the latest collection and `ETag`, reorder the complete `items` array, then send it as `blocks` to `PUT /v1/forms/{formId}/blocks` with `If-Match`.

<Note>
  Focused block endpoints address top-level blocks. Nested questions are stored in a group block's `children` array; replace that parent group to edit its nested collection.
</Note>

## Update a theme without changing content

```bash theme={null}
curl --request PATCH \
  "https://api.onlyform.com/v1/forms/507f1f77bcf86cd799439011" \
  --header "X-API-Key: $ONLYFORM_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "definition": {
      "theme": {
        "font": "Inter",
        "corners": 1,
        "colors": {
          "primary": "#3969D9",
          "primaryForeground": "#FFFFFF"
        }
      }
    }
  }'
```

Because `blocks` and `settings` are omitted, the request does not replace them.

## Prevent lost updates with ETags

`GET /v1/forms/{formId}` returns an `ETag` header. Send that value as `If-Match` when updating the form:

```bash theme={null}
curl --request PATCH \
  "https://api.onlyform.com/v1/forms/507f1f77bcf86cd799439011" \
  --header "X-API-Key: $ONLYFORM_API_KEY" \
  --header 'If-Match: "m5v8w7g0"' \
  --header "Content-Type: application/json" \
  --data '{"name":"Updated contact form"}'
```

If another request changed the form after you fetched it, Onlyform returns `412 FORM_VERSION_MISMATCH`. Fetch the latest form, reconcile your change, and retry with the new ETag.

<Warning>
  If you send `definition.blocks`, you replace the ordered block collection. Always start from the latest form and preserve every block you intend to keep.
</Warning>

## Plan restrictions

API writes enforce the same plan limits as the dashboard. Features such as custom CSS, redirects, branding removal, payment fields, file uploads, signatures, gates, and maximum question counts may require a paid plan.

A restricted change returns `403 PLAN_RESTRICTION`.

## Deletion behavior

`DELETE /v1/forms/{formId}` moves the form to trash and returns `204 No Content`. It does not permanently remove the form.

<CardGroup cols={2}>
  <Card title="Create a form" icon="file-circle-plus" href="/api-reference/forms/create-form">
    Create blocks, settings, and a theme in one request.
  </Card>

  <Card title="Add a block" icon="square-plus" href="/api-reference/forms/create-form-block">
    Add a question or content block without replacing the form.
  </Card>
</CardGroup>
