> ## 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.

# Update a template

> PUT /v1/templates/:slug — create-or-republish a template from a manifest. Idempotent provisioning + in-place regeneration.

<Info>**PUT** `https://api.craftkit.dev/v1/templates/{slug}`</Info>

Create-or-republish the template at `slug` — the idempotent companion to
[`POST /v1/templates`](/api-reference/create-template). It re-runs the **same** manifest → layout
synthesis as create, so a template authored **before** a layout feature shipped (for example image
blocks) can be regenerated **in place** instead of orphaning its slug to a `-v2`.

* **Template does not exist** → it is created and published as version 1 → `201`.
* **Template exists** → a **new version** (n+1) is published and becomes current → `200`.

Use it from a provisioning script to make template setup idempotent across environments: the first
run creates, every subsequent run republishes.

<Note>
  **Existing renders are never affected.** Each render pins its own `templateVersionId`, so
  republishing only changes what *new* renders use — previously rendered PDFs are immutable.
</Note>

## Authorization

<ParamField header="Authorization" type="string" required>
  `Bearer ck_live_…` — must own the project that holds (or will hold) the template.
</ParamField>

## Path

<ParamField path="slug" type="string" required>
  The canonical kebab-case slug. **The URL slug wins** — any `slug` in the body is ignored.
</ParamField>

## Body

The body is the same shape as [create](/api-reference/create-template) (minus `slug`, which comes
from the URL).

<ParamField body="name" type="string" required>Display name (1–120 chars). Replaces the stored name on republish.</ParamField>
<ParamField body="description" type="string">Optional, up to 280 chars. Replaces the stored description on republish.</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`).
    </ParamField>
  </Expandable>
</ParamField>

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

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

## Response

`200` (republished) or `201` (created):

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

<ResponseField name="slug" type="string">The canonical slug (the one in the URL).</ResponseField>

<ResponseField name="currentVersionNumber" type="number">
  The newly published version. `1` on create; incremented by one on every republish.
</ResponseField>

<ResponseField name="manifest" type="object">The stored manifest, echoed back.</ResponseField>

<Note>
  Each republish creates a new version even when the manifest is unchanged — calling PUT N times
  leaves N versions in history. This is intentional (it keeps `currentVersionNumber` advancing) and
  harmless to existing renders.
</Note>

## 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`    | The slug is occupied by another (possibly soft-deleted) template — [DELETE](/api-reference/delete-template) it first or pick a new slug. |
| 409    | `version_conflict` | Concurrent republishes raced for the next version number — retry.                                                                        |

```bash cURL theme={null}
curl -X PUT https://api.craftkit.dev/v1/templates/charter-handover \
  -H "Authorization: Bearer $CRAFTKIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Charter Handover",
    "manifest": {
      "variables": [
        { "key": "booking.code", "label": "Booking code", "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 200 theme={null}
{
  "id": "…",
  "slug": "charter-handover",
  "currentVersionNumber": 2,
  "manifest": { "variables": [ "…" ], "loops": [ "…" ] }
}
```

<Tip>
  Prefer PUT over `DELETE` + `POST` when you only need the latest layout: it keeps the same template
  `id` and slug, and leaves prior renders intact.
</Tip>
