# Configure the widget from your page

> The window.__m24 object the widget reads, covering visitor identity, page context for the AI agent and custom product card renderers.

Source: https://docs.message24.net/developers/website-chat/javascript-api/

The widget is configured by the script tag and by a global `window.__m24` object that your page defines. It does not expose methods or emit events: there is no JavaScript call to open, close or reset the chat. Appearance, the welcome message and the visitor details form are set in the channel's settings in Message24, not from your page.

## Reference

| Setting | Type | Read when |
|---|---|---|
| `data-channel-key` (script attribute) | string | Once, when the script runs. Required. |
| `window.__m24.visitor` | `{ name?: string, phone?: string }` | When the widget loads and when it creates the visitor's session. |
| `window.__m24.pageData` | JSON object | Every time the visitor sends a message. |
| `window.__m24.renderers.product` | `(product) => HTMLElement` | Every time a product card renders. |
| `window.__m24.renderers.productList` | `(products) => HTMLElement` | Every time a message with product cards renders. |

Always extend the object rather than replacing it, so separate scripts do not overwrite each other:

```js
window.__m24 = window.__m24 || {};
```

## Visitor identity

If your site knows who the visitor is, pass their name and phone so the conversation in the inbox is labelled with them instead of "Visitor":

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

How it is used:

- **Set it before the widget script loads.** The widget reads it once while starting up.
- **It is captured when the visitor first opens the chat** in that browser, which is when the session is created. The name and phone are applied to the contact when the first message is sent. Changing `window.__m24.visitor` afterwards does not update an existing conversation.
- **If the channel has a visitor details form**, the form is still shown, pre-filled with these values, and the visitor confirms or edits them.
- **Limits:** `name` up to 120 characters, `phone` up to 40. The phone number is normalized by Message24; one that cannot be read as a phone number makes session creation fail with `400`.

Without `visitor` and without a visitor details form, the conversation appears as "Visitor".

## Page context

Tell the AI agent and the team what the visitor is looking at. Set `window.__m24.pageData` to any JSON object:

```html
<script>
  window.__m24 = window.__m24 || {};
  window.__m24.pageData = {
    pageType: "product",
    productId: "sku_123",
    name: "Blue Sneakers",
    category: "Shoes",
    price: 79.99,
    currency: "USD",
    inStock: true
  };
</script>
```

With each message, the widget sends a page context made of:

| Field | Value |
|---|---|
| `url` | `location.pathname` at send time. Maximum 2,048 characters. |
| `title` | `document.title` at send time. Maximum 300 characters. |
| `data` | `window.__m24.pageData` at send time, if set. Must be valid JSON, 8 KB at most. |

The page context is stored on the message and as the conversation's current page.

The AI agent gets a one-line summary of the current page built from these keys in `data`, plus `url`:

| Summary item | Keys read from `data`, first one present wins |
|---|---|
| Page | `name`, `productName`, `title`, `product_title`, then the page `title` |
| Price | `price` or `amount`, with `currency` or `currencyCode` |
| Stock | `inStock` or `available` (boolean) |
| Category | `category` |
| Type | `pageType` or `type` |

Other keys, such as `productId` above, are stored with the message but are not part of that summary.

:::caution
If `pageData` is larger than 8 KB, every send fails with `400` and the visitor sees "Message failed to send". Keep it to the few fields that describe the page.
:::

In a single-page app, update `pageData` as the route changes. See [Single-page apps and route changes](/developers/website-chat/embed-widget/#single-page-apps-and-route-changes).

## Custom product cards

When the AI agent sends products, the widget shows product cards: an image, the name, price, a stock badge, and a **View** link when the business has product links set up. You can render your own cards instead, for example to add an "Add to cart" button wired to your store.

Register a renderer on `window.__m24.renderers`. It receives a product object and must return an `HTMLElement`:

```html
<script>
  window.__m24 = window.__m24 || {};
  window.__m24.renderers = {
    // Replace individual product cards
    product: function (product) {
      // product = { name, price, currency, imageUrl, url, inStock, externalId }
      var card = document.createElement("div");
      card.style.cssText = "border:1px solid var(--m24-border);border-radius:12px;overflow:hidden;background:var(--m24-surface);";

      if (product.imageUrl) {
        var img = document.createElement("img");
        img.src = product.imageUrl;
        img.alt = product.name;
        img.style.cssText = "width:100%;height:160px;object-fit:cover;display:block;";
        card.appendChild(img);
      }

      var info = document.createElement("div");
      info.style.padding = "12px";

      var name = document.createElement("div");
      name.textContent = product.name;
      name.style.cssText = "font-weight:600;font-size:14px;color:var(--m24-text);";
      info.appendChild(name);

      var row = document.createElement("div");
      row.style.cssText = "display:flex;align-items:center;justify-content:space-between;margin-top:8px;";

      var price = document.createElement("div");
      price.textContent = new Intl.NumberFormat(undefined, {
        style: "currency", currency: product.currency || "USD",
        minimumFractionDigits: 0, maximumFractionDigits: 2
      }).format(product.price);
      price.style.cssText = "font-weight:700;font-size:15px;color:var(--m24-text);";
      row.appendChild(price);

      var btn = document.createElement("button");
      btn.textContent = "Add to Cart";
      btn.style.cssText = "border:none;border-radius:8px;padding:6px 14px;font-size:13px;font-weight:600;cursor:pointer;background:var(--m24-accent);color:#fff;";
      btn.addEventListener("click", function (e) {
        e.preventDefault();
        e.stopPropagation();
        // Wire to your cart API here
        btn.textContent = "Added!";
        btn.disabled = true;
      });
      row.appendChild(btn);

      info.appendChild(row);
      card.appendChild(info);
      return card;
    },

    // Or replace the entire product list container (optional)
    // productList: function (products) {
    //   var grid = document.createElement("div");
    //   grid.style.cssText = "display:grid;grid-template-columns:1fr 1fr;gap:8px;";
    //   products.forEach(function (p) { ... });
    //   return grid;
    // }
  };
</script>
```

`renderers.product` replaces each card inside the widget's own layout; a message with more than one product is shown as a horizontal carousel. `renderers.productList` receives all of a message's products as an array and replaces the whole group, so you control the layout. If both are set, `productList` wins.

### Product fields

| Field | Type | Description |
|---|---|---|
| `name` | string | Product name. |
| `price` | number | Selling price. For a product with variants, the lowest variant price. |
| `currency` | string | The business's currency code, for example `"IQD"` or `"USD"`. |
| `imageUrl` | string, optional | First product image. A signed, time-limited URL: display it, don't store it. |
| `url` | string, optional | Link to the product on the business's website. Present only when the business has set up product links. |
| `inStock` | boolean | Stock availability. |
| `externalId` | string | The product's ID in the business's own catalog, useful for calling your cart API. |

### CSS variables

Your elements render inside the widget's Shadow DOM, so your site's stylesheets do not apply to them. Use inline styles, and these variables to match the widget:

```css
var(--m24-accent)       /* Brand colour, from the channel's Primary Color */
var(--m24-text)         /* Main text: #1a1a1a */
var(--m24-text-muted)   /* Muted text: #8b8b8b */
var(--m24-surface)      /* Background: #ffffff */
var(--m24-surface-alt)  /* Alt background: #f4f5f7 */
var(--m24-border)       /* Border: #e8e8e8 */
```

### Errors

If a renderer throws, the widget logs `[m24] Custom product renderer error` to the console. If it returns something that is not an `HTMLElement`, it logs `[m24] Custom product renderer must return an HTMLElement`. In both cases the card area stays empty; the widget does not fall back to its default card, so test your renderer against real products.

### Tips

- Call `e.stopPropagation()` in button handlers inside a card so the click does not reach anything underneath.
- Define `window.__m24.renderers` before the widget script loads, so the first cards already use it.
- To render with React or Vue, create a container element, mount your component into it with `createRoot` or `createApp`, and return the container.
- Try your renderer on the [test page](/developers/website-chat/overview/#test-a-channel) before deploying.

## Links in messages

Replies can contain Markdown links (`[text](url)`) and bare `http` or `https` URLs, which the widget turns into links and link cards. A link to the same host as the page opens in the same tab; any other link opens in a new tab. `javascript:`, `data:`, `vbscript:` and `file:` links are shown as plain text.
