Update Layers
Add, duplicate, replace, edit, delete or restack layers on a template's pages, including saved animation timing
Updated
/v1/studio/templates/{templateId}/update-layerscurl -X PATCH "https://api.orshot.com/v1/studio/templates/<TEMPLATE_ID>/update-layers" \
-H "Authorization: Bearer <ORSHOT_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"operations": [],
"base_updated_at": "<BASE_UPDATED_AT>",
"embed_user_id": "<EMBED_USER_ID>"
}'const res = await fetch("https://api.orshot.com/v1/studio/templates/<TEMPLATE_ID>/update-layers", {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer <ORSHOT_API_KEY>",
},
body: JSON.stringify({
"operations": [],
"base_updated_at": "<BASE_UPDATED_AT>",
"embed_user_id": "<EMBED_USER_ID>"
}),
});
const data = await res.json();import requests
response = requests.patch(
"https://api.orshot.com/v1/studio/templates/<TEMPLATE_ID>/update-layers",
headers={"Authorization": "Bearer <ORSHOT_API_KEY>"},
json={
"operations": [],
"base_updated_at": "<BASE_UPDATED_AT>",
"embed_user_id": "<EMBED_USER_ID>"
},
)
data = response.json()$ch = curl_init("https://api.orshot.com/v1/studio/templates/<TEMPLATE_ID>/update-layers");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PATCH");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer <ORSHOT_API_KEY>",
"Content-Type: application/json",
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
"operations" => [],
"base_updated_at" => "<BASE_UPDATED_AT>",
"embed_user_id" => "<EMBED_USER_ID>"
]));
$data = json_decode(curl_exec($ch), true);
curl_close($ch);require "net/http"
require "json"
uri = URI("https://api.orshot.com/v1/studio/templates/<TEMPLATE_ID>/update-layers")
req = Net::HTTP::Patch.new(uri)
req["Authorization"] = "Bearer <ORSHOT_API_KEY>"
req["Content-Type"] = "application/json"
req.body = {
"operations": [],
"base_updated_at": "<BASE_UPDATED_AT>",
"embed_user_id": "<EMBED_USER_ID>"
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)Enterprise Only
This endpoint is available only to first party clients like the Orshot MCP server and workspaces on the Enterprise plan. Checkout the Enterprise Pricing to get access.
Change individual layers of a template without re-sending the design: add a layer, copy one, replace one, edit any of its fields (including saved animation timing and motion, which Update Template Modifications can only apply per render), delete it, or change its stacking order. Only pages whose content changed get a new thumbnail and animated preview; every other page keeps the one that already matches it.
Which update endpoint?#
| You want to change | Use |
|---|---|
| Name, tags, canvas size, or the entire design at once | Update Template |
| Pages: add, duplicate, replace, edit settings, delete, reorder | Update Pages |
| Layers on a page: add, replace, edit (incl. saved animation timing), delete, restack | Update Layers (this page) |
| Values of existing dynamic layers: text, images, colors, style, position | Update Template Modifications |
Endpoint#
https://api.orshot.com/v1/studio/templates/:templateId/update-layersQuery Parameters#
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
includeThumbnails | Boolean | No | false | When true, the response waits for the new thumbnails of the changed pages. Otherwise they are generated in the background. |
includeVideoThumbnails | Boolean | No | false | When true and at most 2 changed pages are animated, the response waits for their animated previews. Otherwise they are generated in the background. |
Request Examples#
// Store new entrance timing on a layer (by element id or parameter id)
await fetch("https://api.orshot.com/v1/studio/templates/12345/update-layers", {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer <ORSHOT_API_KEY>",
},
body: JSON.stringify({
operations: [
{
op: "update",
parameter_id: "headline",
page_id: "a1b2c3d4-5e6f-7890-abcd-ef1234567890", // or page: 1
set: {
transitions: { enter: { type: "slideInUp", duration: 0.6 }, showAt: 1.2 },
style: { color: "#FFFFFF" }
}
}
]
}),
});// Add a logo on page 2 above the background, move a badge to the top, delete a sticker
await fetch("https://api.orshot.com/v1/studio/templates/12345/update-layers", {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer <ORSHOT_API_KEY>",
},
body: JSON.stringify({
operations: [
{
op: "add",
page: 2, // or page_id: "b2c3d4e5-6f78-9012-bcde-f23456789012"
element: { type: "image", content: "https://cdn.example.com/logo.png", x: 40, y: 40, width: 160, height: 60 },
position: { above: "bg-shape" }
},
{ op: "move", element_id: "badge", position: "top" },
{ op: "delete", element_id: "sticker-3" }
]
}),
});{
"success": true,
"changed": true,
"template": {
"id": 12345,
"name": "Spring Sale Carousel",
"updated_at": "2026-10-02T09:20:11.042Z",
"pages": [
{ "page": 1, "page_id": "a1b2c3d4-...", "name": "Cover" },
{ "page": 2, "page_id": "b2c3d4e5-...", "name": "Offer" }
]
},
"results": [
{ "index": 0, "op": "add", "status": "applied", "page": 2, "page_id": "b2c3d4e5-...", "element_id": "7f6e5d4c-..." },
{ "index": 1, "op": "move", "status": "applied", "page": 2, "page_id": "b2c3d4e5-...", "element_id": "badge" },
{ "index": 2, "op": "delete", "status": "applied", "page": 2, "page_id": "b2c3d4e5-...", "element_id": "sticker-3" }
]
}{
"error": "operations[0] (update): 2 layers have parameterId \"cta\" (on pages 2, 3). Add \"page_id\" or \"page\" to pick one, or target it by element_id. Nothing was saved.",
"code": "ambiguous_layer",
"details": [
{ "index": 0, "op": "update", "code": "ambiguous_layer", "error": "2 layers have parameterId \"cta\" (…)" }
]
}Rate Limits#
This endpoint shares the Enterprise limit of 30 requests per minute per API key with the other template endpoints.
URL Parameters#
| Parameter | Type | Required | Description |
|---|---|---|---|
templateId | Integer | Yes | The ID of the template to update |
Request Body#
| Parameter | Type | Required | Description |
|---|---|---|---|
operations | Array | Yes | 1 to 50 layer operations, applied in order. See Operations. |
base_updated_at | String | No | The template's updated_at from your last read. If the template changed since, nothing is written and you get 409. Without it, operations apply to the latest version. |
embed_user_id | String | No | Assign the template to an embed user, same as Update Template Modifications. |
Operations#
Every operation has an op. Operations on an existing layer name it with parameter_id (a dynamic layer, as listed in the modifications of Get Studio Template) or element_id (any layer: returned in results by your own add and duplicate operations, and readable in the full design by first-party clients such as the Orshot MCP server). Add page_id or page to search one page only; it is required when the same id appears on several pages, and for add.
Page number or page id#
Anywhere an operation names a page, send either one:
| Field | Type | What it is |
|---|---|---|
page_id | String | The page's id. It never changes, so it keeps pointing at the same page after pages are added, deleted or reordered. Read it from Get Studio Template or from template.pages in any response of this endpoint. |
page | Integer | The page number, starting at 1. Simple for a one-off edit, but it changes when pages are added, deleted or moved. |
If you send both, they must point at the same page, otherwise the request is refused with page_target_mismatch.
op | Fields | What it does |
|---|---|---|
add | page_id or page, element (layer object), position (optional) | Adds a layer, on top by default. A missing id is generated. |
duplicate | layer target, position (optional, default just above the original) | Copies the layer with a new id. |
replace | layer target, element | Replaces the whole layer. It keeps its id. |
update | layer target, set (object) | Changes any layer fields: content, style, position, dimensions (these three merge, so { "style": { "color": "#F00" } } keeps other styles), transitions, motion, parameterId, opacity, … A null value removes the field. Flat x, y, width, height are accepted and only change what you send. |
delete | layer target | Deletes the layer. |
move | layer target, position | Changes the stacking order. |
position is "top", "bottom", { "above": "<layer id>" } or { "below": "<layer id>" }, relative to layers on the same page. Only the moved layer changes; other layers keep their zIndex.
Layer objects use the same shape as the elements in pages_data (see Anatomy of a Template); type is one of text, image, element, shape, video, waveform. Layer operations never change page numbers.
Response Fields#
| Field | Type | Description |
|---|---|---|
success | Boolean | true when the request was valid |
changed | Boolean | false when the operations changed nothing. Nothing is written and the version does not move. |
template.id | Integer | Template ID |
template.name | String | Template name |
template.updated_at | String | The new version. Send it as base_updated_at on your next write. |
template.pages | Array | Every page: page (number), page_id, name |
template.thumbnails | Object | Only with ?includeThumbnails=true: the new thumbnails of the changed pages |
results | Array | One entry per operation: index, op, status (applied or unchanged), page, page_id, element_id, parameter_id when the layer has one, and source_element_id for duplicate |
warnings | Array | Only when present: non-blocking notes, such as unknown motion keys |
Errors#
Every error explains what to change. A 400 means nothing was saved: operations are all-or-nothing.
| Status | code | When |
|---|---|---|
| 400 | operations_required, operations_empty, too_many_operations | operations is missing, empty, or longer than 50 |
| 400 | unknown_op, invalid_operation, use_element_field | An operation isn't an object with a known op, or sends the layer as layer instead of element |
| 400 | layer_target_required, layer_not_found, ambiguous_layer, layer_target_mismatch | The target layer is missing, doesn't exist, matches several layers, or element_id and parameter_id disagree |
| 400 | page_target_required, page_not_found, invalid_page, invalid_page_id, page_target_mismatch | The page given doesn't exist, or add has no page |
| 400 | invalid_layer_object, duplicate_layer_id, layer_id_mismatch | The layer object is malformed, its id is taken on that page, or it doesn't match the layer it replaces |
| 400 | set_required, field_not_settable | update has no set, or tries to change id |
| 400 | position_required, invalid_position | move without a valid position, or a position that names a layer on another page |
| 400 | allow_reindex_not_applicable | allow_reindex was sent; it only applies to Update Pages |
| 400 | invalid_motion | The operations introduce a structural motion error |
| 400 | template_too_large, no_pages | The design would exceed 10 MB, or the template has no pages yet |
| 403 | The API key, plan or trial access doesn't include this endpoint | |
| 404 | The template isn't in your workspace | |
| 409 | base_updated_at is stale: fetch the template again and redo the operations on that version | |
| 423 | An approval flow locks the template; see Approvals |
Ready to automate?
Start rendering images, PDFs and videos from your templates in under 2 minutes. Free plan, no credit card.
Get your API key- Image, PDF and video generation via API
- Visual editor with AI and smart layouts
- Zapier, Make, MCP and 50+ integrations
- White-label embed for your own app
- 100 free credits a month, no credit card required