Install erxes Messenger

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

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

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

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

The shipped nginx config blocks embedding

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.

Was this helpful?