Skip to content

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.

Markdownllms.txt

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.

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

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

<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 for every field.

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

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.

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 />;
}

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.

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;
}
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:

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

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.

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:

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

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.