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

# Embed the agent in your app

> Show the Replyful agent inside your own product as a side panel or a full page, themed with your app's colours

Besides the floating chat bubble, you can place the agent inside your logged-in product. Two layouts are available:

| Layout | What it looks like | On phones |
| - | - | - |
| **Side panel** | Opens next to your content, like an assistant sidebar. You open and close it from your own button. | A drawer from the right, with a backdrop that closes it |
| **Full page** | Fills an area of your app, next to your own navigation, with a large greeting and the message box at the bottom | Full width |

Both use a separate script, `rf-embed.min.js`. It renders into an element you place, so your own layout decides where the agent sits and how big it is. Your existing bubble snippet keeps working unchanged, and you can run both on the same site.

## Choose the layout

1. In your [Replyful dashboard](https://app.replyful.com), open **Channels** → **Chat** and pick your chatbot.
2. Open the **Embed** tab and choose **Side panel** or **Full page**.
3. Copy the code snippet shown below the layout cards.

Changing the layout only changes which snippet the dashboard shows. A site that already has the bubble snippet keeps showing the bubble until you replace it.

## Full page

Add the script once, then place a container where the agent should appear. Give the container the size of your content area:

```html theme={null}
<script src="https://chat.replyful.com/rf-embed.min.js" async></script>

<main data-replyful-embed="YOUR_CHANNEL_ID" data-layout="page" style="height: 100%"></main>
```

## Side panel

Place the panel next to your main content and open it from your own button. Your layout decides whether the panel pushes the content aside or overlays it. Keep `hidden` on the panel and `disabled` on the button: the script takes over both once it has loaded, so nothing flashes and no early click is lost.

```html theme={null}
<script src="https://chat.replyful.com/rf-embed.min.js" async></script>

<aside id="assistant" data-replyful-embed="YOUR_CHANNEL_ID" data-layout="panel" style="width: 400px" hidden></aside>

<button type="button" id="assistant-toggle" aria-controls="assistant" aria-expanded="false" disabled>Assistant</button>
<script>
  function setupAssistant() {
    var button = document.getElementById("assistant-toggle");
    var assistant = window.ReplyfulEmbed.get(document.getElementById("assistant"));
    button.disabled = false;
    button.addEventListener("click", function () { assistant.toggle(); });
    assistant.on("open", function () { button.setAttribute("aria-expanded", "true"); });
    assistant.on("close", function () { button.setAttribute("aria-expanded", "false"); });
  }
  if (window.ReplyfulEmbed) setupAssistant();
  else window.addEventListener("replyful-embed:ready", setupAssistant);
</script>
```

On screens narrower than 768px the panel opens as a drawer over your page. Tapping the backdrop or pressing <kbd>Esc</kbd> closes it, and focus returns to the button that opened it.

<Note>
  If an ancestor of the panel uses `transform`, `filter` or `contain`, browsers keep the mobile drawer inside that ancestor instead of covering the screen. Place the panel outside such elements.
</Note>

## Container attributes

| Attribute | Description |
| - | - |
| `data-replyful-embed` | Your chatbot channel ID. Required. |
| `data-layout` | `panel` or `page`. Defaults to the layout chosen on the Embed tab, or `panel` when that is the floating bubble. |
| `data-header` | `true` adds a simple header with the title, a new-conversation button and, for panels, a close button. Leave it out to draw your own. |
| `data-title` | Title for the header and the chat frame. Defaults to "Chat". |
| `data-identity` | `required` keeps the message box locked until `identify` succeeds, so a signed-in user's first message is never anonymous. See [Signed-in users](#signed-in-users). |
| `data-theme-*` | Overrides one theme colour, for example `data-theme-primary="#4f46e5"`. See [Theming](#theming). |

The script finds containers on load and any added later, so it works with single-page apps. Removing a container cleans up its agent.

## JavaScript API

`window.ReplyfulEmbed` is available once the script has loaded. Because the script loads `async`, check for it and otherwise wait for the `replyful-embed:ready` event on `window`.

```javascript theme={null}
// The agent in an element that has data-replyful-embed
const assistant = window.ReplyfulEmbed.get(element);

// Or mount it yourself
const assistant = window.ReplyfulEmbed.mount(element, {
  channelId: "YOUR_CHANNEL_ID",
  layout: "panel",
  theme: { primary: "#4f46e5" },
});

assistant.open();
assistant.close();
assistant.toggle();
assistant.isOpen();
assistant.newConversation();
assistant.identify(token); // see User identification
assistant.setTheme({ background: "#0b0b0f", foreground: "#f5f5f5" });
assistant.destroy();

const unsubscribe = assistant.on("open", () => {});
```

Events: `open`, `close`, `iframe-ready`, `identity-verified`, `identity-failed`. A full-page agent is always open, so `close()` does nothing there.

The global is `ReplyfulEmbed`, not the bubble's `replyful`, so both can run on the same page.

## Signed-in users

Inside your product your users are usually signed in, so link their conversations to them. Sign a token on your backend exactly as described in [User identification](/user-identification), and pass it to `identify` each time the chat loads:

```javascript theme={null}
const assistant = window.ReplyfulEmbed.get(element);
assistant.on("iframe-ready", async () => {
  const { token } = await fetch("/api/replyful-token").then((r) => r.json());
  assistant.identify(token);
});
```

Fetching the token on `iframe-ready` means it's always fresh, even when the panel is first opened hours after the page loaded, so a short expiry is fine.

To hold the chat until your user is identified, add `data-identity="required"` (or pass `identity: "required"` to `mount`). The message box then stays locked until the token is verified, and shows an error if verification fails. This only affects the container you put it on; it is a convenience for your signed-in users, not access control.

To refuse anonymous visitors altogether, turn on **Require identification** for the chatbot. See [Require identification](/user-identification#require-identification).

## Theming

The agent takes on your app's look automatically. It reads the background behind the container, the container's text colour and font, and whether your page is light or dark. If your app defines shadcn-style CSS variables (`--primary`, `--muted`, `--border`, `--radius` and so on), it uses those too; any it doesn't define are mixed from your background and text colour, so a dark page gets dark borders and bubbles without any variables at all. When your app switches between light and dark mode, the agent follows.

To set colours explicitly, pass `theme` to `mount`, call `setTheme`, or add `data-theme-*` attributes. Explicit values win over detected ones. Supported keys:

`background`, `foreground`, `primary`, `primary-foreground`, `muted`, `muted-foreground`, `secondary`, `secondary-foreground`, `accent`, `accent-foreground`, `border`, `input`, `radius`, `color-scheme` (`light` or `dark`) and `fontFamily`.

Values must be plain colours or lengths. A `fontFamily` must be a system font or a font on Google Fonts. Without a `primary`, buttons and links use your chatbot's brand colour.
