> ## Documentation Index
> Fetch the complete documentation index at: https://widgetic.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Canvas Embedding

> Let your customers create and edit widgets with Widgetic's canvas and AI inside your platform

Two of Widgetic's pieces let your customers make widgets without leaving your product:

* The **widget creator** is the whole Widgetic canvas. Your customer draws frames, describes a widget to the AI in the chat, watches it being built, changes its settings and publishes it.
* The **composition editor** changes the texts, pictures, colours and fonts of a widget your customer owns, beside a live preview, and saves it.

Both are mounted with the [UI SDK](https://widgetic.com/docs/partners/ui-components.md) and use an [editor session](https://widgetic.com/docs/partners/editor-sessions.md) for one of your [managed users](https://widgetic.com/docs/partners/managed-users.md). This page covers what is specific to them.

> [!NOTE]
>
> **Preview.** These pieces are new. They are built and tested, and you can try them now, but small details can still change while this label is on. Tell us what you find at [support@widgetic.com](mailto:support@widgetic.com).

## Before you start

**1. Make a managed user for the customer**

One for each of your customers. See [Managed Users](https://widgetic.com/docs/partners/managed-users.md). Widgetic's `id` for the managed user is what your server uses to make sessions.

**2. Make an editor session on your server**

Ask for the surfaces the piece needs: `["creator", "editor", "files"]` for the widget creator, `["editor", "files"]` for the composition editor. See [Editor Sessions](https://widgetic.com/docs/partners/editor-sessions.md).

**3. Mount the piece in your page**

With the UI SDK's `mountModule`, as below. Give the container a height: the piece fills it.

## The widget creator

```javascript theme={null}
import { mountModule } from '@widgetic/ui-sdk';

const creator = await mountModule('widget-creator', {
  container: '#creator', // give it a height: it fills its container
  canvasId: savedCanvasIdOfThisCustomer, // optional: the one `canvasChanged` gave you last time
  getToken: async () => (await fetch('/widgetic-session', { method: 'POST' })).json(), // { token, expiresAt }
  theme: 'light',
  on: {
    ready: ({ canvasId }) => {},
    canvasChanged: ({ canvasId }) => rememberForNextVisit(canvasId),
    widgetRenamed: ({ widgetId, name }) => setTitleOfTheCustomersWork(name),
    widgetPublished: ({ widgetId, version }) => showInMyProduct(widgetId),
    limitReached: ({ message, code }) => offerMore(),
    error: ({ message, code, fatal, sessionEnded }) => report(code, message),
  },
});

// later:
await creator.unmount();
```

### Options

| Option | |
| - | - |
| `getToken` | **Required.** Returns the editor session's token, as for every piece. It is called once before the creator appears (a `getToken` that fails fails the mount), and again when the token is about to run out or the API refuses it. |
| `canvasId` | The canvas to open, as `canvasChanged` gave it to you. It has to be this customer's. Without one the creator opens the customer's latest canvas, or makes a new one when they have none. |
| `widgetId` | The widget of that canvas to open (needs `canvasId`). |
| `startWithNewWidget` | Start from an empty canvas with a new widget instead of the latest canvas. |
| `presets` | `'pick'` (the default) or `'off'`. A customer who has made nothing yet is shown your [presets](https://widgetic.com/docs/partners/managed-users.md#preset-widgets) before the creator opens, and gets a copy of the one they pick. `'off'` never shows them. See [Offer your presets to a new customer](#offer-your-presets-to-a-new-customer). |
| `startFromPreset` | The key of one of your presets. A customer who has made nothing yet gets a copy of it without being asked, and the creator opens on it. |
| `presetLabels` | The words of the picker, for a tone of your own: `{ heading, intro, startFromScratch, startFromScratchHint, copying, copyFailed, presetGone, checking }`. A shortcut for the eight `presetPicker.` messages: a word that `messages` also says is `messages`'s. Any word neither says stays English. |
| `locale`, `messages` | The creator's language: the plural forms, numbers and dates, and the words as **one** object of message keys and texts for the whole creator (its own words, the chat's, the canvas's and the picker's). A key you leave out stays English. `creator.setLanguage({ locale?, messages? })` changes them while it is on screen. See [The widget creator](https://widgetic.com/docs/partners/ui-components.md#the-widget-creator). |
| `mediaLibrary` | Your own media library for the file pickers of the settings panel. See [Show your own media library](https://widgetic.com/docs/partners/ui-components.md#show-your-own-media-library). Pictures dropped on the canvas and the chat's attachments do not use it: they go through Widgetic's upload call into your own storage, which every session requires (see [Every session needs your own storage](https://widgetic.com/docs/partners/editor-sessions.md#every-session-needs-your-own-storage)). |
| `widgetBuilderUrl` | The origin the live previews of widgets are served from. Default: Widgetic's. |
| `theme` | As for every piece: a mode, or your colours, corner radius and font. The creator follows the mode, your neutral colours, your brand colour and the font; its corners are not themed yet (see [Not there yet](#not-there-yet)). See [Make the pieces look like your product](https://widgetic.com/docs/partners/ui-components.md#make-the-pieces-look-like-your-product). |
| `apiUrl` | As for every piece. |

### Events

| Event | Payload | |
| - | - | - |
| `ready` | `{ canvasId }` | The canvas is on screen. `canvasId` is `null` for a new, empty one. For a customer who is shown the picker it comes after they chose. |
| `presetChosen` | `{ key, canvasId, widgetId, widgetName, warnings, automatic }` | A starting point was chosen, by the customer or by your `startFromPreset`. See [Offer your presets to a new customer](#offer-your-presets-to-a-new-customer). |
| `canvasChanged` | `{ canvasId }` | The creator opened a canvas or made one. Keep the id and give it back as `canvasId` next time. |
| `widgetRenamed` | `{ widgetId, name }` | The main widget has this name. Said when the canvas opens and each time the customer renames it, never twice in a row for the same name, so you can keep a title for the customer's work. |
| `widgetPublished` | `{ widgetId, version? }` | The customer published a version of a widget. |
| `limitReached` | `{ message, code? }` | The customer ran into a limit. The creator has told them already. See [When a limit is reached](#when-a-limit-is-reached). |
| `reloadRequested` | | The creator would have reloaded the page to recover. Unmount it and mount a new one. |
| `error` | `{ message, code?, fatal, during?, sessionEnded? }` | See [When something goes wrong](#when-something-goes-wrong). |

Methods on `creator.module`: `getCanvasId()` (the canvas that is open now, or `null`). `creator.setTheme(theme)` changes its look (a mode, or settings: see [Make the pieces look like your product](https://widgetic.com/docs/partners/ui-components.md#make-the-pieces-look-like-your-product)). `creator.setLanguage({ locale?, messages? })` changes its language, and `creator.module.defaultMessages` and `creator.module.checkMessages(messages)` list its messages and check yours (see [The widget creator](https://widgetic.com/docs/partners/ui-components.md#the-widget-creator)). `creator.unmount()` takes the creator away.

### Offer your presets to a new customer

A customer who has made nothing yet would meet an empty canvas. When you have made [presets](https://widgetic.com/docs/partners/managed-users.md#preset-widgets), the creator shows them first: a page of cards (a thumbnail, a title and a line about it, in your order) and a *Start from scratch* button. A pick is **copied** into the customer's own work, as a canvas with one widget built on a fork of the preset's code, and the creator opens on the copy. The customer changes the copy, never your preset.

* **Who sees it.** Only a customer with no canvas, on a page that gives no `canvasId` and does not ask for `startWithNewWidget`. A customer who has work goes straight to their latest canvas, and costs one question to the API. A page that gives `canvasId` back, as `canvasChanged` asks, or sets `presets: 'off'`, asks nothing at all.
* **What your page hears.** `presetChosen` carries the copy: `key`, `canvasId`, `widgetId` and `widgetName` are `null` when the customer chose *Start from scratch*, `automatic` is `true` for `startFromPreset`, and `warnings` are the sentences the copy answered with for you (a part that could not be copied exactly). The creator then reports the copy's canvas with its own `canvasChanged`, and `ready` when it is open.
* **A starting point for everyone.** `startFromPreset: 'welcome-quiz'` copies that preset for a customer with nothing, with no picker. When it cannot be had (the key is not one of your presets any more, the API is not answering), the picker is shown, or the plain creator when `presets` is `'off'`, and your page hears the `error` below.
* **Never in the way.** Whatever goes wrong while looking for starting points (you have none, a request is refused or lost), the creator opens as it always did and your page hears one `error` with `fatal: false` and `during: 'presets'`. A session that is over is the creator's to announce, once, as `fatal`. A copy that fails keeps the picker on the screen with a sentence for the customer, and pressing again sends the same `Idempotency-Key`, so a copy whose answer was lost is not made twice. A preset you took away meanwhile leaves the picker with its own sentence.
* **Words and look.** The picker is drawn with the piece's `theme`, and every word is in `presetLabels`. A word that is not a non-empty text, a `presets` that is not `'pick'` or `'off'` and a `startFromPreset` that is not a preset key are refused when the creator is made, with a message that says which.
* **Thumbnails** are the `thumbnailUrl` you gave the preset, and only `http` and `https` addresses are drawn. They load from your page, so a strict Content-Security-Policy needs their origin in `img-src`. What does not load is replaced by the first letter of the title.
* **What a copy costs.** Each copy forks a repository and there is no limit per customer on how many a customer makes: the API's ordinary rate limit for the customer's address is the only brake.

The picker calls two routes that exist for editor sessions only, so they are not in the public API or the SDKs. If you want a picker of your own, read the list with `listPartnerPresets` and make the copy with `copyPresetForManagedUser` from your server (see [Preset Widgets](https://widgetic.com/docs/partners/managed-users.md#preset-widgets)), and give the copy's `canvasId` and `widgetId` to the creator.

### Remember where the customer was

The creator writes nothing to your `localStorage`, `sessionStorage`, cookies or address bar, and never opens a window or reloads your page. What it remembers lives in memory until `unmount()`. So keep the id from `canvasChanged` with your customer's record and give it back as `canvasId` the next time, and the customer finds their work where they left it.

### What the creator makes, and how you find it

Everything the creator makes is filed under the session's managed user, and the session can open only that customer's canvases, widgets and compositions. Another customer's canvas answers `404`, exactly as if it did not exist.

* **Find a customer's work** from your server with the filter you already know. Your key sees all of it:
  ```bash theme={null}
  curl -sS -H "Authorization: Bearer $WIDGETIC_PARTNER_KEY" \
    "https://api.widgetic.com/v1/widgets?managedUserExternalId=your-platform-user-123"
  ```
  `GET /canvases` and `GET /compositions` take the same `managedUserId` and `managedUserExternalId` filters.
* **Show the result in your product** the way you show any widget: a [composition](https://widgetic.com/docs/api/compositions.md) of the widget, and the embed code. The creator makes compositions of the widgets it builds to show them with sample values; you can list them with the filter above or make your own on the same widget with your key.
* **Private to your account.** A widget a customer builds is never made public in Widgetic's catalogue by a session. Publishing a version saves it and the customer can go back to an earlier one.
* **Your key reaches everything**, filed under a customer or not. Deciding which of your customers may see which widget stays in your own platform.

### When a limit is reached

The AI is paid for by your account (see [Credits & Billing](https://widgetic.com/docs/partners/credits-billing.md)), and your credits and your plan are yours: **a customer is never told what they hold.** When one is used up, the API refuses the session with one plain sentence, "This is not available right now. Please try again later.", and the creator shows exactly that, in the chat with a *Retry* button and in a toast, and keeps working: the canvas, the chat and the other widgets are still there.

It also tells **your page**, with `limitReached` `{ message: 'The visitor reached a limit.', code }`. The `code` is what the API answered:

| `code` | What happened |
| - | - |
| `BILLING_INSUFFICIENT_CREDITS` | Your account is out of credits. Every customer is refused until you add some. |
| `AUTHZ_PLAN_RESTRICTION` | Your plan does not include what the customer asked for. |
| `COST_LIMIT_EXCEEDED` | This customer used their allowance for the hour or the day. It comes back by itself, and it is this customer's alone. |
| `EDITOR_SESSION_CREDIT_LIMIT_REACHED` | This session spent the `max_credits` you gave it when you made it. It is this session's alone: make a new one if the customer may spend more. See [A limit for one session](https://widgetic.com/docs/partners/editor-sessions.md#a-limit-for-one-session). |
| `LIMIT_REACHED` | The customer pressed an "Upgrade" button in the chat. The creator never opens a page or a window for it: what an upgrade means in your product is for you to show. |

Your page hears each `code` once in half a minute, however many requests are refused meanwhile. Ordinary rate limiting (`RATE_LIMIT_EXCEEDED`) is **not** a limit: the creator asks the customer to wait a moment and tells your page nothing.

The allowance of the hour or the day is counted for each customer on their own, so one customer reaching it refuses and tells nobody else. Credits and plan are your account's, so when they run out every customer is refused.

A customer's upload that cannot be kept, because you have set up no storage of your own (`409` `PARTNER_STORAGE_REQUIRED`), is not a limit: your page hears no `limitReached` for it, and the customer reads the same plain sentence. You find out when you make the session, which is refused for the same reason. See [Every session needs your own storage](https://widgetic.com/docs/partners/editor-sessions.md#every-session-needs-your-own-storage).

A paused or not yet activated partner account is not a limit either: the customer reads one plain sentence, "This service is not available right now. Please contact the website that sent you here.", and your page hears no `limitReached` for it. See [Account Status](https://widgetic.com/docs/partners/account-status.md).

### When something goes wrong

The creator shows a problem to the customer itself. The `error` event is for your page. `fatal: true` means the creator cannot go on, and it has said so on its own screen:

* **The session is over** (`sessionEnded: true`, for example `EDITOR_SESSION_EXPIRED` or `EDITOR_SESSION_REVOKED`). Mount again, with a new token from `getToken`.
* **The `canvasId` you gave is not this customer's** (`during: 'canvas'`, code `NOT_FOUND`). Mount again without it.

Either way no `ready` comes, and the creator stops asking the API. With `fatal: false` the creator carries on (a failed token refresh that will be retried, or the starting points not working: `during: 'presets'`, see [Offer your presets to a new customer](#offer-your-presets-to-a-new-customer)).

### What it leaves alone

* **The keyboard.** The canvas's shortcuts (Delete, the arrows, `v`, `r`, `h`, Ctrl+A and so on) work only while the focus is inside the creator, so a customer who clicked on your page keeps every key of it. Your page's own zoom (Ctrl or Cmd with `+`, `-`, `0`, Ctrl with the wheel) works everywhere. A customer gives the creator the keyboard by clicking on it or tabbing into it.
* **Your page's scroll.** The chat follows its newest message inside its own box and never scrolls your page.
* **Your `<head>` and `<body>`.** The creator adds one `<style>` with font rules to `<head>` (named `Widgetic UI …`, shared with the other pieces and never added twice, so it stays when the creator is unmounted) and draws everything else inside its shadow root.

## The composition editor

Let a customer change the texts, pictures, colours and fonts of a widget they own, beside a live preview, and save it. It is the editor of Widgetic's own site, drawn in your page.

Create the composition on your server with your key and file it under the customer. A session opens only compositions filed under its customer. Then mount the editor on it:

```javascript theme={null}
const editor = await mountModule('composition-editor', {
  container: '#editor', // give it a height: it fills its container
  compositionId: 'cmp_123', // the composition of the customer who is on the page; one editor edits one composition
  getToken: async () => (await fetch('/widgetic-session', { method: 'POST' })).json(), // { token, expiresAt }
  on: {
    ready: ({ name, widgetId }) => showTitle(name),
    dirty: ({ dirty }) => markUnsaved(dirty),
    saved: ({ savedAt }) => markSaved(savedAt),
    error: ({ message, code, fatal, sessionEnded }) => report(code, message),
  },
});

// later: saves what is unsaved first, then takes the editor away
await editor.unmount();
```

| Option | |
| - | - |
| `compositionId` | **Required.** The composition to edit. |
| `mediaLibrary` | Your own media library for the file pickers. |
| `googleFontsApiKey` | A Google Fonts Web Fonts API key for the font picker's list. Without one the picker lists a short set of popular families. |
| `autosaveDelayMs` | How long after the last change the editor saves by itself. Default 2500. `0` turns autosave off. |
| `locale` | A BCP 47 tag such as `de`: plural forms, numbers and dates, and the names of the months and weekdays in its calendars. |
| `messages` | The editor's words in your language: one object for the editor, its file picker and its rich text editor. A key you leave out stays English. See [Show the pieces in your customers' language](https://widgetic.com/docs/partners/ui-components.md#show-the-pieces-in-your-customers-language). |

* **Saving.** The editor saves by itself shortly after the last change. `editor.module.save()` saves what is unsaved now, `editor.module.flush()` saves everything unsaved and waits until it is (both resolve `true` when nothing is left unsaved), and `editor.module.isDirty()` tells whether anything is unsaved. A save is safe to retry: it carries an `Idempotency-Key`.
* **`unmount()` saves first**, for up to 10 seconds, so closing the editor never loses the last edits. If some could not be saved you hear an `error` with the code `UNSAVED_ON_UNMOUNT`. `editor.module.unmount({ discard: true })` throws unsaved edits away instead.
* **Problems** are shown to the customer by the editor itself, with "Try again" where that helps. `error` is for your page, and adds `during`, `status`, `retryable`, `sessionEnded` and `correlationId` to the usual fields. `fatal: true` means the editor cannot go on: the composition cannot be opened, or the session is over (`sessionEnded: true`). Mint a new session or take the editor away. Quote `correlationId` to Widgetic's support.
* **Another customer's composition** is answered with `404`, and the editor says it cannot be opened.
* **One composition editor per page** for now. A second is refused with a clear message.

## Content Security Policy and CORS

If your page sends a Content-Security-Policy, the widget creator needs, in addition to what the File Browser needs, `frame-src` and `connect-src` for the preview hosts, and the composition editor needs `frame-src https://cdn.widgetic.com`. The full list is in [UI Components](https://widgetic.com/docs/partners/ui-components.md#content-security-policy). You do not have to ask Widgetic for anything to allow your page (CORS): the API accepts a session token from any origin (see [Editor Sessions](https://widgetic.com/docs/partners/editor-sessions.md#browsers)). Bind the session to your page with `allowed_origin` instead.

## Not there yet

* **One creator and one composition editor per page.** Their state (the signed-in customer, the chat, the file lists) belongs to the page, so a second one is refused with a clear message. Unmount the first before mounting another.
* **No composition steps in the creator.** The creator's *Edit* step (a widget's compositions) and *Embed* step are off in a session, and a page cannot switch them on. Edit compositions with the composition editor.
* **A chat attachment over 10 MB is refused** before anything is sent. A session cannot use the resumable upload.
* **Stop gives the credit back only if the model had not started writing.** The *Stop* button in the chat cancels the generation on the server: the request to the model is aborted, the run ends at its next checkpoint, and nothing is built or saved from it. A generation stopped before the model began to write returns its credit. Once the answer has begun to stream it keeps the credit, because a generation costs one whole credit and there is no part to return.
* **Tabs share a generation only inside one browser.** A second tab of the same browser is told that a generation is running on the widget, follows it until it ends and then brings the widget up to date, so the customer is not left generating the same widget twice. A customer who opens the same widget in another browser or on another device is not told. Only a widget's id and the operation's id pass between the tabs, and each tab checks with the API, with its own sign-in, whether the operation is its customer's.
* **Size.** The widget creator is about 5.5 MB (1.3 MB gzip), downloaded when you mount it and not before. A customer who previews a PDF downloads two more files then (1.8 MB, 0.5 MB gzip). To have it ready sooner, call `preload('widget-creator')` on a `WidgeticUI` when the page that will host it opens (see [Several pieces on one page](https://widgetic.com/docs/partners/ui-components.md#several-pieces-on-one-page)).
* **The creator takes your mode, neutral colours, brand colour and font, but not yet your corners.** A dark look, or colours of your own for the background, the text, the soft surfaces and the lines, restyles its panels, toolbar, menus, dialogs and chat, and the font applies to all of its text. Your `primary` colour is the creator's accent: the selected tool, the sliders, the buttons that act, the chat's Send button and what is on them, darker under the pointer. The text and icons on an accent are your `primaryForeground` when you give one, else white or a dark ink, whichever reads on your colour (a light yellow gets dark text). Without a `primary` the accents are Widgetic's own green and blue. The canvas board and the live widget previews stay light on purpose (they show the widget as your customers will see it), and the chat's message bubbles keep their own light colours. The handles the canvas draws on its board, a few accents the creator sets in script and the colours of a status (the green of a published widget, the red of an error) do not take your brand colour, and most of its corners are Widgetic's own, so `radius` changes very little of it. The composition editor and the File Browser follow a dark theme. See [Make the pieces look like your product](https://widgetic.com/docs/partners/ui-components.md#make-the-pieces-look-like-your-product).

## Next Steps

> [card] [Editor Sessions](https://widgetic.com/docs/partners/editor-sessions.md)
>
> Make the token on your server

> [card] [UI Components](https://widgetic.com/docs/partners/ui-components.md)
>
> The UI SDK, the File Browser and your own media library

> [card] [Managed Users](https://widgetic.com/docs/partners/managed-users.md)
>
> File your customers' work under them

> [card] [Compositions](https://widgetic.com/docs/api/compositions.md)
>
> Show a widget in your product with the embed code


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.