# Embed the widget

> Add the Message24 chat widget to a plain HTML site, a React app or a Next.js app, and keep page context accurate across client-side route changes.

Source: https://docs.message24.net/developers/website-chat/embed-widget/

The widget is a single script tag. Copy your channel key from the channel's Web Chat settings in Message24 (the **Embed Code** box), then add the script once per page.

## Plain HTML

Paste the snippet before `</body>` on every page where the chat should appear:

```html
<script src="https://message24.net/widget.js" data-channel-key="YOUR_CHANNEL_KEY" async></script>
```

That is all that is required. The widget mounts once the DOM is ready, whether the script loads early or late.

To pass visitor details or page data, define `window.__m24` in a script that runs **before** the widget script:

```html
<script>
  window.__m24 = window.__m24 || {};
  window.__m24.visitor = { name: "Alice", phone: "+964 750 000 0000" };
  window.__m24.pageData = { productId: "sku_123", category: "Shoes", price: 79.99 };
</script>
<script src="https://message24.net/widget.js" data-channel-key="YOUR_CHANNEL_KEY" async></script>
```

See [Configure the widget from your page](/developers/website-chat/javascript-api/) for every field.

### Rules for the script tag

- **Load it from `https://message24.net/widget.js`.** The widget sends its API requests to the origin its script was loaded from. A copy of `widget.js` served from your own domain would send them to your domain instead. The file is served with no-cache headers, so you always get the current version.
- **Keep `data-channel-key` on the tag itself.** The widget finds its configuration by reading that attribute from the running script, or from the first `script[data-channel-key]` on the page.
- **Loading it twice is harmless.** If `#m24-widget-root` already exists, the second copy does nothing.

## React

Load the widget once from your app shell. Mounting it in a component that re-renders or unmounts on navigation gains nothing: the widget attaches itself to `<body>` and lives for the whole page.

```jsx
import { useEffect } from "react";

export default function App() {
  useEffect(() => {
    window.__m24 = window.__m24 || {};
    window.__m24.visitor = { name: currentUser?.name ?? "", phone: currentUser?.phone ?? "" };

    if (document.getElementById("m24-widget-script")) return;
    const s = document.createElement("script");
    s.id = "m24-widget-script";
    s.src = "https://message24.net/widget.js";
    s.async = true;
    s.dataset.channelKey = "YOUR_CHANNEL_KEY";
    document.body.appendChild(s);
  }, []);

  return <YourApp />;
}
```

:::note
The widget has no teardown method. Removing `#m24-widget-root` hides the bubble, but its polling keeps running until the page reloads. Load it once and leave it in place.
:::

## Next.js

Use a client component that sets `window.__m24` and injects the script, and render it from your root layout. The example uses the App Router; with the Pages Router, render the same component from `pages/_app.tsx`.

```tsx
// app/chat-widget.tsx
"use client";

import { useEffect } from "react";

export default function ChatWidget({ channelKey }: { channelKey: string }) {
  useEffect(() => {
    const w = window as any;
    w.__m24 = w.__m24 || {};

    if (document.getElementById("m24-widget-script")) return;
    const s = document.createElement("script");
    s.id = "m24-widget-script";
    s.src = "https://message24.net/widget.js";
    s.async = true;
    s.dataset.channelKey = channelKey;
    document.body.appendChild(s);
  }, [channelKey]);

  return null;
}
```

```tsx
// app/layout.tsx
import ChatWidget from "./chat-widget";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <ChatWidget channelKey="YOUR_CHANNEL_KEY" />
      </body>
    </html>
  );
}
```

A product page can then publish its data:

```tsx
"use client";

import { useEffect } from "react";

export function ProductContext({ product }: { product: { id: string; name: string; price: number } }) {
  useEffect(() => {
    const w = window as any;
    w.__m24 = w.__m24 || {};
    w.__m24.pageData = { productId: product.id, name: product.name, price: product.price, pageType: "product" };
    // Clear it when the visitor navigates away.
    return () => {
      w.__m24.pageData = undefined;
    };
  }, [product]);
  return null;
}
```

Clearing the data in the effect's cleanup means it is removed before the next page renders, so a visitor who moves from one product to another never sends the old product's data.

## Single-page apps and route changes

The widget stays mounted across client-side navigation and keeps the same conversation. You do not need to reload or re-inject it.

Page context is read at the moment the visitor sends a message:

- `url` is `location.pathname` at send time, so it follows your router automatically.
- `title` is `document.title` at send time.
- `data` is `window.__m24.pageData` at send time.

So the only thing to do on a route change is keep `window.__m24.pageData` accurate: set it when a page with meaningful data renders, and clear it when the visitor leaves. Stale page data tells the AI agent the visitor is looking at a product they have already left.

## Logging a user out

The conversation belongs to a visitor ID stored in the browser's `localStorage` under `m24_visitor_id`. If several people share a browser, for example a user logs out and another logs in, remove that key and reload so the next person starts a fresh conversation:

```js
localStorage.removeItem("m24_visitor_id");
location.reload();
```

## Content Security Policy

If your site sends a `Content-Security-Policy` header, allow:

- `script-src https://message24.net` for `widget.js`.
- `connect-src https://message24.net` for the API calls and the event stream.
- `style-src 'unsafe-inline'`, because the widget injects its own `<style>` element and inline styles.
- `img-src` and `media-src` for the hosts that serve images, audio and video in replies, and for the business logo URL.
