# Messenger Widget

Embed the Frontline Messenger widget on your website and verify the conversation round trip


Add the Frontline Messenger to your website so visitors can exchange messages with your team. If you run a different erxes release, copy the snippet generated by that installation.

## Prerequisites

- A running erxes installation with the Frontline API and UI enabled.
- Access to configure a Messenger integration and its channel/brand settings.
- A deployed `frontline-widgets` application reachable from visitors' browsers. It is a separate Nx project under `apps/frontline-widgets` and is not deployed by starting Core UI and the gateway alone. See [Self-Hosting](https://erxes.io/docs/self-hosting).
- Access to your website's HTML, layout, or tag manager.

## How the pieces fit

The host page loads `messengerBundle.js`, a small IIFE built by esbuild from [`apps/frontline-widgets/src/index.ts`](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/apps/frontline-widgets/src/index.ts). The loader injects a launcher and an iframe (`erxes-messenger-iframe`) whose `src` is the directory the bundle was loaded from, so `https://widgets.example.com/messengerBundle.js` implies the widget app at `https://widgets.example.com/`. The iframe app talks to the gateway's `/graphql` (HTTP and `graphql-ws` subscriptions) through `widgetsMessengerConnect`, `widgetsInsertMessage`, `widgetsConversations`, and the other `widgets*` operations. None require a user login.

The install dialog builds the bundle URL from `REACT_APP_WIDGETS_URL` ([`frontend/plugins/frontline_ui/src/modules/utils.ts`](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/frontend/plugins/frontline_ui/src/modules/utils.ts)): `window.env.REACT_APP_WIDGETS_URL` first, then the build-time value, then a default derived from the current host. A `<subdomain>` placeholder is replaced with the leftmost host label.

## Install

### 1. Create the Messenger integration

Open your Frontline channel/integration settings, create an erxes Messenger integration, and configure the channel, branding, and team settings. Save it, open its **Install script** action, and use **Preview** to check the appearance before embedding.

The generated snippet sets `window.erxesSettings.messenger.integrationId` and loads `messengerBundle.js`. Older `brand_id` snippets or legacy Script Manager instructions describe a different widget version.

### 2. Add the generated snippet to your site

Place it near the end of `body`, or in a shared layout that runs in the browser. Replace the placeholder values with the integration ID and widget URL from your installation:

```html
<script>
  window.erxesSettings = {
    ...window.erxesSettings,
    messenger: {
      integrationId: "YOUR_MESSENGER_INTEGRATION_ID",
    },
  };

  (function () {
    const script = document.createElement("script");
    script.src = "https://widgets.example.com/messengerBundle.js";
    script.async = true;
    document.body.appendChild(script);
  })();
</script>
```

The spread preserves any other widget settings already on the page (the generated snippet assigns the object outright, so merge it yourself when combining widgets). Set `erxesSettings` **before** loading the bundle. The integration ID is browser-visible configuration; no app token is needed.

### 3. Verify the conversation round trip

Open the site in a fresh browser session:

1. `messengerBundle.js` loads in the Network panel.
2. The widget iframe loads from the expected host and the launcher appears.
3. Send a test message, find it in the intended Frontline inbox/channel, and reply as a team member.
4. The visitor receives the reply. Also test a mobile viewport and a reload.

A visible launcher proves the loader ran; the message round trip verifies the integration, gateway, and WebSocket connection together.

## Single-page applications

Load the snippet in browser-only code after mount; never touch `window`/`document` during SSR. The loader detects an existing `erxes-messenger-container`, and if the bundle executes again it re-sends the current `erxesSettings`, `localStorage.erxes` data, and theme to the live iframe instead of mounting a second one. This is how a per-page `integrationId` change takes effect. Changing the settings object alone does not run the loader.

## Add a form alongside Messenger

Frontline forms have their own **Install script** action (`install-form.tsx`). The form widget reads `window.erxesSettings.forms`, an array of `{ form_id, channel_id }`, loaded with `formBundle.js`. Configure both widgets before loading either bundle:

```html
<div data-erxes-embed="YOUR_FORM_ID"></div>

<script>
  window.erxesSettings = {
    ...window.erxesSettings,
    messenger: { integrationId: "YOUR_MESSENGER_INTEGRATION_ID" },
    forms: [
      { form_id: "YOUR_FORM_ID", channel_id: "YOUR_CHANNEL_ID" },
    ],
  };

  ["messengerBundle.js", "formBundle.js"].forEach(function (bundle) {
    const script = document.createElement("script");
    script.src = "https://widgets.example.com/" + bundle;
    script.async = true;
    document.body.appendChild(script);
  });
</script>
```

The form dialog also offers an embedded target (`data-erxes-embed`) or a modal trigger (`data-erxes-modal="YOUR_FORM_ID"` on any button). Verify a submission lands in the configured channel.

## Knowledge content

The Messenger integration itself carries a `knowledgeBaseTopicId`; when set, the widget shows an FAQ section from the Frontline knowledge base. Frontline UI also includes a topic **embed script** dialog that generates a `knowledgeBaseBundle.js` snippet. The `frontline-widgets` build only produces `messengerBundle.js` and `formBundle.js`, so that generated snippet has no bundle to load. For a full public help center, deploy `apps/help-center` instead (see [Help Center](https://erxes.io/docs/app-help-center)).

## Self-hosted widget configuration

Two variables matter:

- `REACT_APP_WIDGETS_URL`: where the install dialog points snippets. In the container, `docker-entrypoint.sh` writes all `REACT_APP_*` env vars to `/js/env.js` at start, so it can be set at run time; the nginx config substitutes `NGINX_HOST`/`NGINX_PORT`.
- `REACT_APP_API_URL`: the gateway URL the iframe app calls. It is **inlined at build time** by `rspack.config.ts` (default `http://localhost:4000`); build the image with the correct value.

```bash
pnpm nx show project frontline-widgets
pnpm nx build frontline-widgets   # depends on compile:index + compile:form (esbuild -> *Bundle.js)
```

Deploy the whole `dist/apps/frontline-widgets` output, not just the bundles. The iframe loads `index.html` and its assets from the same directory.

> [!WARNING]
> **The shipped nginx config blocks embedding**
>
> [`apps/frontline-widgets/nginx/default.conf`](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/apps/frontline-widgets/nginx/default.conf) adds `X-Frame-Options: SAMEORIGIN` on `location /`. As shipped, the messenger iframe can only render on the same origin as the widgets host, so it will not appear on your website. Remove or relax that header (and any equivalent `frame-ancestors` CSP rule) for the widget routes.

## Troubleshooting

- `messengerBundle.js` returns 404: wrong `REACT_APP_WIDGETS_URL` or an incomplete deploy. The bundles are esbuild outputs copied as build assets; redeploy the full `dist` tree.
- The launcher renders but the iframe is blank: the iframe `src` is the bundle's directory, so make sure `index.html` and assets exist there. Then check the browser console for a `REACT_APP_API_URL` baked at build time that points at localhost.
- The iframe refuses to load: `X-Frame-Options: SAMEORIGIN`/`DENY` or CSP `frame-ancestors` on the widgets host (see the callout above), or CSP on your site blocking the frame/script.
- "Integration not found" or the widget never connects: `integrationId` does not match a messenger-kind integration in this tenant (`widgetsGetMessengerIntegration`/`widgetsMessengerConnect` fail). Recopy the ID from the install dialog.
- GraphQL requests blocked by CORS: the site origin is not in the gateway's `WIDGETS_DOMAIN`, `ALLOWED_DOMAINS`, or `ALLOWED_ORIGINS` list.
- Messages send but replies never arrive: the `graphql-ws` subscription on `wss://<gateway>/graphql` is blocked by a proxy that does not upgrade WebSockets.
- Settings vanish: a later snippet overwrote `window.erxesSettings`. Merge with the spread pattern above.
- Duplicate launchers after client-side navigation: the loader is idempotent, so duplicates mean two different widget hosts or a tag manager firing the snippet plus a hard-coded one.
- Generated `knowledgeBaseBundle.js` returns 404: `frontline-widgets` does not build it. Use the messenger FAQ or `apps/help-center`.

For Google Tag Manager, put the generated snippet in a Custom HTML tag with triggers for the intended pages, and make sure no second installation loads the same widget.

