> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developers.synthesia.io/docs/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developers.synthesia.io/docs/_mcp/server.

# Interactive Avatar: Add the widget to your website

The managed widget is the Synthesia-hosted way to add an Interactive Avatar to your website. Create a deployment in Studio, paste its snippet into your page, and Synthesia runs the experience end to end: the widget, the conversation and the call. You don't need API keys, an LLM or a room of your own.

For full control over the avatar's brain (your own LLM and speech stack) and over room management, use the [bring-your-own integration](/docs/ia-overview) instead.

## Add the widget

Adding the widget takes three steps. You do the first two in Studio and the last one on your page.

### 1. Create a deployment

In Studio, create a deployment for your avatar. A deployment holds the widget's settings, such as which avatar to show, how it looks and which sites may use it. Its type decides which snippet you get on the **Deploy** tab. Both snippets load the same widget, but they give you different options:

| Type       | Snippet       | What you get                                                                                                            |
| ---------- | ------------- | ----------------------------------------------------------------------------------------------------------------------- |
| **Widget** | A script tag  | A floating bubble or an inline frame. You can control it from your page and hold it back until the visitor consents.    |
| **Embed**  | An iframe tag | The avatar inline at a fixed size, with no script on your page. There's no bubble, no page API and no consent deferral. |

Use an Embed if your page allows frames but not third-party scripts. Otherwise, use a Widget. An Embed is always open in place, so the deployment's corner, starting state and logo settings don't apply to it.

### 2. Allow your site's origin

The widget only runs on sites you allow, so a copy of your snippet on someone else's site stays blank. Add every origin that shows the widget to the deployment's allowed origins. The match is exact, so `https://www.example.com` and `https://example.com` are two different origins. If your site uses both, add both.

A subdomain pattern such as `https://*.example.com` covers the subdomains of `example.com`, but not `example.com` itself. You can't use `*` on its own to allow any origin, so list each origin or use a subdomain pattern.

On an origin that isn't allowed, the browser refuses to show the widget frame (see [Errors](/docs/ia-widget-page-api#errors) for what your page gets). If another site shows your page inside its own frame, read [Frames and page builders](#frames-and-page-builders) too.

### 3. Paste the snippet into your page

Copy the snippet from the **Deploy** tab and paste it into your page. A Widget's script snippet looks like this:

```html
<script
  async
  src="https://interactive-avatars.synthesia-embeds.io/v1/runtime.js"
  data-deployment-id="dep_…"
></script>
```

An Embed gives you an iframe snippet instead, described in [Embed with an iframe](#embed-with-an-iframe). If your site sends security headers, check [Security policies](/docs/ia-widget-security-policies) before you go live.

## Script snippet attributes

The script snippet reads its settings from `data-` attributes on the script tag. The snippet from Studio already has the deployment ID. Add the other attributes when you need them:

| Attribute            | Required | Description                                                                                                                                                                                                                                                             |
| -------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `data-deployment-id` | Yes      | The ID of the deployment to show.                                                                                                                                                                                                                                       |
| `data-mode`          | No       | `bubble` (default) shows a floating bubble in a corner. `inline` puts a frame inside your layout.                                                                                                                                                                       |
| `data-target`        | No       | In inline mode, a CSS selector for the container the frame goes into. The container must exist when the script runs. If it doesn't, or you leave this out, the frame goes right after the script tag. If the tag is in `<head>`, the frame goes at the end of the page. |
| `data-consent`       | No       | Set to `deferred` to load nothing until you call `init()`. See [Consent deferral](/docs/ia-widget-privacy#consent-deferral).                                                                                                                                                 |
| `data-title`         | No       | The frame's accessible title, which screen readers announce.                                                                                                                                                                                                            |

You set the bubble's corner, colors and logo on the deployment in Studio, not with attributes.

The bubble shows above your page's content, whatever its `z-index`. Dialogs and popovers your page opens later show above the bubble. On screens narrower than 640 pixels, such as phones, the bubble starts minimized and opens full screen.

> **Note**
>
> The bubble can cover a cookie banner in the same corner. Pick another corner in Studio, or use [consent deferral](/docs/ia-widget-privacy#consent-deferral).

## Use the custom element

The script also defines a custom element, `<synthesia-interactive-avatar>`. It's often easier to work with than a script tag if you use a framework or a CMS block, because you can add and remove it like any other element. Load the script without a deployment ID, and give the element the same attributes without the `data-` prefix:

```html
<script
  async
  src="https://interactive-avatars.synthesia-embeds.io/v1/runtime.js"
></script>
<synthesia-interactive-avatar
  deployment-id="dep_…"
  mode="inline"
></synthesia-interactive-avatar>
```

In inline mode, the frame goes inside the element. In bubble mode, the element stays empty and the bubble floats over the page.

### Single-page apps

Here's how the widget behaves when your app changes routes or replaces parts of the page:

* If your app inserts a script tag later, its widget mounts when the tag is inserted. If your app renders the same tag again while the widget is on the page, nothing changes, and a call in progress keeps going.
* When an element leaves the page, its widget is destroyed. The exception is an element your app puts back synchronously, in the same task, as frameworks do when they move an element. A new element with the same deployment ID that takes its place in the same task mounts as well. Moving an inline element reloads its frame, because browsers reload any iframe that moves.
* Each deployment mounts once per page. A second tag or element with the same deployment ID is ignored while the first one's widget is on the page, and takes over once that widget is gone.

### Navigations that replace the page body

Some libraries navigate by replacing the page's `<body>` instead of loading a new page. Here's what happens to the widget with the common ones:

* With Turbo Drive, you get a new, idle widget in the new body, and a call in progress ends with the old body. A tag with consent deferral comes back deferred, so call `init()` again, as on the first page load.
* With htmx's `hx-boost`, the body's content is replaced and the tag in it runs again, with the same result. Keep the tag inside the body.
* Swup, jquery-pjax and Barba swap a container rather than the whole body, so the bubble stays where it is.
* When the browser restores a cached copy of the page on a back navigation, the widget mounts once, without a second bubble.

An inline frame inside a `data-target` container moves with that container. If your app takes the container out of the page and puts it back, the frame comes back with it, reloaded. After a body swap, a tag with the same deployment ID in the new body takes over and places the frame where its own `data-target` says. If the new body has no such container, the frame goes right after the tag, as on a first load of that page.

## Embed with an iframe

The iframe snippet is a single tag with no script on your page. It loads `https://interactive-avatars.synthesia-embeds.io/v1/dep_…` in a 400 × 620 frame.

Keep the `allow` and `referrerpolicy` attributes the snippet comes with. `allow` gives the widget the microphone and autoplay, and `referrerpolicy` lets it check your origin. [Security policies](/docs/ia-widget-security-policies) explains both.

## Frames and page builders

If another site shows your page inside a frame, the origin check covers that site too, not only your page. The widget stays blank unless that site's origin is allowed as well. This happens in:

* Page builder editors and previews, such as Webflow Designer, the WordPress or Elementor customizer and the Shopify theme editor.
* Learning management systems, intranet portals and other sites that show your page in a frame.

Add those origins to the deployment as well. Each frame between the top page and the widget also needs:

* `allow="microphone"`. A frame can only pass on the microphone it was given, so one missing attribute blocks the microphone.
* No `sandbox` attribute, or one that includes both `allow-scripts` and `allow-same-origin`. Without them, the widget can't run.

### Shopify

Shopify serves your store on two origins, so allow both. Add your custom domain and your store's exact `https://your-store.myshopify.com` origin. You can't use a `*.myshopify.com` pattern, because every Shopify store shares the `myshopify.com` domain.

## Load the script from our URL

Always load `runtime.js` from `https://interactive-avatars.synthesia-embeds.io/v1/runtime.js`. Don't copy it to your server, bundle it into your app or serve it through a proxy. The script reads its own URL to find the widget, so a copy on your origin points the frame at your origin and the widget doesn't load.

We also ship fixes to `runtime.js` without any change to your page, and a copy never gets them. Browsers and our edge each cache it for up to five minutes, so a fix reaches your visitors within about ten minutes.

> **Warning**
>
> Don't add an `integrity` attribute (Subresource Integrity). The file changes with every release, so a pinned hash stops the widget from loading after our next release. `crossorigin="anonymous"` is fine, because we serve the script with CORS headers.

## Supported browsers

The widget needs a current browser with WebRTC and microphone access, such as Chrome, Edge, Firefox or Safari. Internet Explorer isn't supported. Your page must use HTTPS, because browsers only give the microphone to secure pages.

## Next steps

Once the widget is on your page, these pages help you control it, keep it compliant and plan for updates.

#### [Security policies](/docs/ia-widget-security-policies)

Allow the widget in your CSP and Permissions-Policy headers.

#### [Page API](/docs/ia-widget-page-api)

Control the widget and react to events from your page.

#### [Privacy and consent](/docs/ia-widget-privacy)

See what the widget sends, and wait for consent.

#### [Versions and status](/docs/ia-widget-versions)

How the widget changes without breaking your snippet.