---
name: update-canvas
description: Update a canvas version including its message, variables, and structured output. Use when user wants to modify an existing canvas/prompt version.
---

# Update Canvas

**Important**: Use `message` for the canvas chat message, never `messages`.

## Which function to use

The three write functions behave very differently. Picking the wrong one is the most common way to
damage a canvas.

| Function | What it does | Creates a version? |
|---|---|---|
| `updateCanvas(canvasId, payload, opts?)` | reads the **latest** version, merges your payload into it, saves the result as a **new version** | **yes, every call** |
| `updateCanvasVersion(versionId, payload)` | raw `PATCH` on **one named version**, in place | no |
| `createCanvasVersion(canvasId, payload)` | builds a brand-new version from the payload alone | yes |

- Editing "the canvas" as a user would mean it → `updateCanvas`.
- Fixing **one specific existing version** → `updateCanvasVersion` with that version's id.
- Starting a version from scratch → `createCanvasVersion`.

### `updateCanvas` creates a new version every time

It is not an in-place edit. Three tweaks in three calls leave three new versions, each inheriting the
previous version's title — a history of identically named entries. Batch related changes into a
single call, and pass a `title` so the history stays readable.

It also cannot be used to fix a promoted version: it always writes a new version, leaving the
promoted one untouched.

### `updateCanvas` only writes to the version

`title` sets the **version** title, not the canvas name. `projectId` and other canvas-level fields are
accepted, return `200`, and are silently discarded — the canvas does not move. Verify with
`getPrompt` before reporting a rename or a move as done.

Moving a **personal draft** into a project is possible, just not here: use
`tela.promoteCanvas(canvasId, projectId)` (see `create-canvas.md`). A canvas already in a project
cannot move at all.

## Parameters

Applies to both `updateCanvas` and `updateCanvasVersion`. Only include what you want to change.

| Parameter | Type | Description |
|-----------|------|-------------|
| `title` | string | **Version** title, not the canvas name |
| `content` | string | HTML content — a legacy path, separate from `message` |
| `markdownContent` | string | Markdown content — same legacy path |
| `configuration` | object | Model configuration. See the replacement rules below |
| `variables` | Variable[] | Variable definitions — **replaces all of them** |
| `message` | Message | Chat message. Carried over from the previous version when omitted |
| `draft` | boolean | Mark as draft |

`promoted` is accepted by the payload type but **does not work**: the call returns `200` and the
version stays unpromoted. Promotion is a separate operation — see [Promoting a version](#promoting-a-version).

### Configuration is replaced, not merged — with one exception

`updateCanvasVersion` sends a raw `PATCH`: the `configuration` you supply **replaces** the stored one.
Sending `{ model: 'gpt-5' }` alone therefore wipes `structuredOutput`, `temperature`, and everything
else on it, without an error. It also fails validation without `type`:

```
422 body.configuration.type — Expected required property
```

The exception is `updateCanvas` with its default `{ merge: true }`, which merges your configuration
into the previous one and explicitly preserves `structuredOutput`. Passing `{ merge: false }` turns it
back into a wholesale replacement, with the same `type` requirement and the same silent loss of
structured output.

**Read the current configuration first and send it back in full** whenever you are not using the
default merge.

## Examples

### Update the message

Always build the message with `tela.createMessage`: it stores your markdown as `markdownContent`
and renders the HTML the editor loads as `content`. Passing a raw string or hand-written HTML as
`content` leaves the two out of sync and shows unformatted text in the app.

```bash
bun --preload ~/.claude/skills/tela-studio/preload.ts -e "
const updated = await tela.updateCanvasVersion('VERSION_ID', {
  message: tela.createMessage('system', '## Task\n\nHelp me with: {task}', 0)
})
console.log(JSON.stringify(updated, null, 2))
"
```

### Edit existing prompt text without losing formatting

Start from the stored markdown, change only what the user asked for, and send the whole thing
back through `createMessage`. Nested and numbered lists, headings, italic, tables, and XML-style
tags such as `<rules>` all survive the round trip; retyping the prompt from memory does not.

```bash
bun --preload ~/.claude/skills/tela-studio/preload.ts -e "
const { version } = await tela.getCanvas('CANVAS_ID')
const markdown = version.message.markdownContent.replace('gpt-4', 'gpt-5')
const updated = await tela.updateCanvas('CANVAS_ID', {
  title: 'v2 - model name fix',
  message: tela.createMessage(version.message.role, markdown, version.message.index)
})
console.log(tela.getCanvasUrl('CANVAS_ID'))
"
```

### Change the model without losing the rest of the configuration

```bash
bun --preload ~/.claude/skills/tela-studio/preload.ts -e "
const current = await tela.getCanvasVersion('VERSION_ID')
const updated = await tela.updateCanvasVersion('VERSION_ID', {
  configuration: { ...current.configuration, model: 'gpt-5' }
})
console.log(JSON.stringify(updated.configuration, null, 2))
"
```

### Update structured output

Write the schema by hand when only some fields are required — `createStructuredOutput` marks every
property required and ignores a `required` option.

```bash
bun --preload ~/.claude/skills/tela-studio/preload.ts -e "
const current = await tela.getCanvasVersion('VERSION_ID')
const updated = await tela.updateCanvasVersion('VERSION_ID', {
  configuration: {
    ...current.configuration,
    structuredOutput: {
      enabled: true,
      schema: {
        properties: {
          summary: { type: 'string', description: 'Brief summary' },
          tags: { type: 'array', description: 'Relevant tags', items: { type: 'string' } }
        },
        title: 'defaultOutput',
        description: 'Always use this output schema.',
        type: 'object',
        required: ['summary']
      }
    }
  }
})
console.log(JSON.stringify(updated, null, 2))
"
```

### Update variables

Variables are replaced wholesale. Send the full list, including the ones you are not changing.

```bash
bun --preload ~/.claude/skills/tela-studio/preload.ts -e "
const updated = await tela.updateCanvasVersion('VERSION_ID', {
  variables: [
    { name: 'document', type: 'file', required: true, description: 'PDF to analyze', processingOptions: { allowMultimodal: true } },
    { name: 'format', type: 'text', required: false, description: 'Output format preference' }
  ]
})
console.log(JSON.stringify(updated, null, 2))
"
```

File options go under `processingOptions`. `tela.createVariable('doc', 'file', { allowMultimodal: true })`
produces the same shape and defaults `required` to `true`.

## Create a new version

`title`, `configuration` (including `type`), and `variables` are all **required**. Send
`variables: []` when the version has none — omitting the key returns
`422 body.variables — Expected required property`.

```bash
bun --preload ~/.claude/skills/tela-studio/preload.ts -e "
const newVersion = await tela.createCanvasVersion('CANVAS_ID', {
  title: 'v2',
  configuration: { model: 'gpt-5', type: 'chat', temperature: 0 },
  variables: [],
  message: { role: 'system', content: 'New version content.', index: 0 }
})
console.log(JSON.stringify(newVersion, null, 2))
"
```

## List the versions of a canvas

`getCanvas` returns only the latest version and `getPrompt` returns none.

```bash
bun --preload ~/.claude/skills/tela-studio/preload.ts -e "
for (const v of await tela.listCanvasVersions('CANVAS_ID'))
  console.log(v.title, v.id, v.promoted ? '(promoted)' : '')
"
```

Returns every version, oldest first. Repeated `updateCanvas` calls produce several versions sharing
one title, so identify them by id when it matters.

## Renaming a version

`updateCanvasVersionTitle` renames a version in place, without creating a new one. This is the only
way to retitle a version deliberately — passing `title` to `updateCanvas` retitles the new version it
creates as a side effect.

```bash
bun --preload ~/.claude/skills/tela-studio/preload.ts -e "
const v = await tela.updateCanvasVersionTitle('VERSION_ID', 'v3 - stricter extraction')
console.log(v.title)
"
```

## Promoting a version

Promotion points production at a version. Before promoting, confirm its suite was reviewed:

```bash
bun --preload ~/.claude/skills/tela-studio/preload.ts -e "
const readiness = await tela.getVersionReadiness('VERSION_ID')
console.log(tela.formatVersionReadiness(readiness))
"
```

See `measure.md`. Promoting an unreviewed version is the same mistake as shipping untested code, with
the difference that it costs money on every run afterwards.


Promotion publishes a version to `production` and is **exclusive**: promoting one version demotes the
previously promoted one. There is no unpromote — the way back is to promote a different version, so
do not delete the version you might want to return to.

Setting `promoted: true` through `createCanvas`, `createCanvasVersion`, or `updateCanvasVersion` does
**not** promote anything. Those calls return `200` and leave `promoted: false`.

```bash
bun --preload ~/.claude/skills/tela-studio/preload.ts -e "
await tela.promoteCanvasVersion('VERSION_ID')
const promoted = await tela.getPromotedVersion('CANVAS_ID')
console.log('promoted:', promoted.id, promoted.title)
"
```

Always read the promoted version back to confirm — this is the operation most likely to be reported
as done when it did not happen.

Scheduled runs use the promoted (Production) version as soon as it changes. If the canvas has active
schedules (`tela.listSchedules({ targetId: canvasId, status: 'active' })`), say that their next run
uses this version. If it looks like recurring work and has none, offer once to schedule it. See
`schedules.md`.

## Deleting a version

```bash
bun --preload ~/.claude/skills/tela-studio/preload.ts -e "
await tela.deleteCanvasVersion('VERSION_ID')
"
```

Deleting is not reversible through the skill. List what you are about to delete and confirm with the
user first. Deleting the promoted version is refused.

## Get the current version

```bash
bun --preload ~/.claude/skills/tela-studio/preload.ts -e "
const version = await tela.getCanvasVersion('VERSION_ID')
console.log(JSON.stringify(version, null, 2))
"
```

## Notes

- **Promoted versions cannot be modified or deleted.** Both are refused with an opaque
  `500 INTERNAL_SERVER_ERROR` rather than a typed error — if a write to a version fails that way, check
  whether it is the promoted one before debugging anything else.
- Failed calls throw `ApiRequestError` carrying `.status` and a `.message` containing the raw response
  body.
- Version-level `content` / `markdownContent` and `message` are independent stores that can diverge.
  Supply the prompt through `message`; `content` and `markdownContent` stay `null` in that case.
- Defaults for new work: `model: 'gpt-5'`, `temperature: 0`.
