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

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