---
title: "Update a template (new version)"
method: PUT
path: "/api/v1/templates/{id}"
tags: ["Template Management"]
---

# Update a template (new version)

`PUT /api/v1/templates/{id}`

## Request

- Base URL: `https://api.imagestep.dev`
- URL: `https://api.imagestep.dev/api/v1/templates/{id}`
- Auth: API key in header `Authorization`

Send only what changes: the body is merged over the current version, and a field it leaves out
keeps its value (`{"width": 1080, "height": 1080}` is a whole update); `""` empties `css` or
`description`. `variables`, when not sent, stay as they are unless `html` or `css` changes, and
are then derived from the new markup. Same limits as create. Never edits in place: the template
moves to `version + 1` — every PUT does, a rename included — and the previous bytes stay readable
as `{id}@{version}`, so a job or automation that pinned a version keeps rendering exactly that.
Built-in ids are 403; to start from one, create your own with its html.

## Path parameters

- `id` string, required

## Headers

- `Idempotency-Key` string

## Request body

- TemplateData: HTML/CSS render template with {{ var }} placeholders; the input of the render_template op
  - `id` string: Template id (`tpl_…`; a built-in's is `builtin-template-…`). A version read by `id@version` carries that spelling here.
  - `name` string: Display name (required on create; at most 200 characters)
  - `slug` string: URL-safe handle, unique per account: lowercase letters, digits and hyphens. Generated from the name when omitted; one you send must be free. `builtin-` is reserved.
  - `description` string: Free text for people; not rendered. `""` on a PUT empties it.
  - `html` string: Body HTML (required on create; ≤ 256 KB as UTF-8). Scripts never run; every request it makes is blocked at render time except `data:` URIs and this product's own asset host.
  - `css` string: Stylesheet (≤ 128 KB as UTF-8). Like the html, it loads nothing but `data:` URIs and this product's own asset host; any other `@import` or `url()` is blocked at render time.
  - `width` integer: Viewport / output width in px (required on create; 1–4096)
  - `height` integer: Viewport / output height in px (required on create; 1–4096)
  - `variables` string[]: Placeholder names (at most 100, each letters, digits, `_` and `.`). Derived from the html/css when omitted. A render fills `{{ name }}` HTML-escaped and `{{{ name }}}` raw; a name the row does not carry renders as nothing.
  - `version` integer: Current version; every PUT increments it and keeps the old one readable as `id@version`
  - `builtIn` boolean: Whether this is a built-in template (read-only; PUT and DELETE are 403)
  - `createdAt` string, date-time: When the template — or, read as `id@version`, that version — was created
  - `updatedAt` string, date-time: When the current version was saved; absent on a version read as `id@version`

## Response `200`

The template, at its new version

## Other responses

- `400`: `invalid_param` naming the field — the merged document fails a create's check (see POST /api/v1/templates), or a new `slug` is malformed, reserved or taken
- `403`: `forbidden` — a built-in template
- `404`: `not_found` on `templateId` — no such template, or it is not yours (a path carrying `@version` names none: a version cannot be edited)
- `409`: Conflict — `error.code` is `idempotency_key_reuse` (the key already means a different request) or `request_in_progress` (the first request under this key has not settled yet; retryable).
- `429`: Rate limited — `error.code` is `rate_limited` and `retryable` is true. `Retry-After` says how many seconds to wait.

---

[API](https://skmtc.dev/imagestep/apis/imagestep-api.md) · [All operations](https://skmtc.dev/imagestep/apis/imagestep-api/llms.txt) · [OpenAPI document](https://skmtc.dev/imagestep/apis/imagestep-api/revisions/5e8e922a3085?raw)
