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

# Create a template

> POST /v1/templates — create a published template from a variable manifest, no builder or hand-written layout required.

<Info>**POST** `https://api.craftkit.dev/v1/templates`</Info>

Create a template from a **manifest** alone. Craftkit synthesizes a layout that renders every
field — scalars as labeled values, `image` variables as images, and **loops as repeating tables** —
and publishes it as version 1. This is the self-serve path for loop/table + image templates (no
dashboard step, no hand-written layout). Pass an explicit `layout` only if you want full control.

## Authorization

<ParamField header="Authorization" type="string" required>
  `Bearer ck_live_…` — the template is created in this key's project.
</ParamField>

## Body

<ParamField body="name" type="string" required>Display name (1–120 chars).</ParamField>

<ParamField body="slug" type="string">
  Kebab-case identifier, unique in the project. Omit to derive one from `name` (a short suffix is
  added if it collides). A provided slug that already exists returns `409`.
</ParamField>

<ParamField body="description" type="string">Optional, up to 280 chars.</ParamField>

<ParamField body="manifest" type="object" required>
  The variable manifest the render payload binds to.

  <Expandable title="manifest">
    <ParamField body="variables" type="VariableDefinition[]" required>
      Scalars. Each: `key` (dot-path), `label`, `dataType`, `required`. An `image` variable is
      rendered as a data-bound `<img>`.
    </ParamField>

    <ParamField body="loops" type="LoopDefinition[]" required>
      Array loops, each rendered as a repeating table. Each: `key` (**dot-free, top-level**),
      `label`, `itemFields[]`. A loop key containing a dot is rejected (`invalid_loop_key`) — see
      [the loop-key rule](/guides/variables-and-loops#the-loop-key-rule).
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="layout" type="object">
  Optional CanvasDocument `contentJson` override. When omitted, it is generated from the manifest.
</ParamField>

<ParamField body="pageConfig" type="object">
  Optional page format (`format`, `orientation`, `margin`, `printBackground`). Defaults to A4 portrait.
</ParamField>

## Response

`201`:

<ResponseField name="id" type="string" />

<ResponseField name="slug" type="string">Final slug (may differ from a derived input if it collided).</ResponseField>
<ResponseField name="currentVersionNumber" type="number">Always `1` for a newly created template.</ResponseField>
<ResponseField name="manifest" type="object">The stored manifest, echoed back.</ResponseField>

## Errors

| Status | code               | Meaning                                                                      |
| ------ | ------------------ | ---------------------------------------------------------------------------- |
| 400    | `invalid_json`     | Body is not valid JSON.                                                      |
| 400    | `invalid_request`  | Envelope, `manifest`, or `pageConfig` failed validation (`issues` included). |
| 400    | `invalid_loop_key` | A loop `key` contains a dot — use a dot-free top-level key.                  |
| 401    | `unauthorized`     | Missing/invalid key.                                                         |
| 409    | `slug_conflict`    | A provided `slug` already exists in this project.                            |

```bash cURL theme={null}
curl -X POST https://api.craftkit.dev/v1/templates \
  -H "Authorization: Bearer $CRAFTKIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Charter Handover",
    "slug": "charter-handover",
    "manifest": {
      "variables": [
        { "key": "booking.code", "label": "Booking code", "dataType": "text", "required": true },
        { "key": "handover.signedBy", "label": "Signed by", "dataType": "text", "required": true },
        { "key": "handover.signatureImageUrl", "label": "Signature", "dataType": "image" }
      ],
      "loops": [
        { "key": "areas", "label": "Inspection areas", "itemFields": [
          { "key": "areaKey", "label": "Area", "dataType": "text" },
          { "key": "condition", "label": "Condition", "dataType": "text" }
        ] }
      ]
    }
  }'
```

```json 201 theme={null}
{
  "id": "…",
  "slug": "charter-handover",
  "currentVersionNumber": 1,
  "manifest": { "variables": [ "…" ], "loops": [ "…" ] }
}
```

<Tip>
  After creating, render it immediately with [`POST /v1/templates/:slug/render`](/api-reference/render-template) —
  send loop arrays at the top level (`data.areas`) and the image as an https URL. See
  [Images & signatures](/guides/images-and-signatures).
</Tip>
