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-widgetsapplication reachable from visitors' browsers. It is a separate Nx project underapps/frontline-widgetsand 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
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.integrationIdand loadsmessengerBundle.js. Olderbrand_idsnippets or legacy Script Manager instructions describe a different widget version.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
erxesSettingsbefore loading the bundle. The integration ID is browser-visible configuration; no app token is needed.Verify the conversation round trip
Open the site in a fresh browser session:
messengerBundle.jsloads in the Network panel.- The widget iframe loads from the expected host and the launcher appears.
- Send a test message, find it in the intended Frontline inbox/channel, and reply as a team member.
- 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.shwrites allREACT_APP_*env vars to/js/env.jsat start, so it can be set at run time; the nginx config substitutesNGINX_HOST/NGINX_PORT.REACT_APP_API_URL: the gateway URL the iframe app calls. It is inlined at build time byrspack.config.ts(defaulthttp://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.jsreturns 404: wrongREACT_APP_WIDGETS_URLor an incomplete deploy. The bundles are esbuild outputs copied as build assets; redeploy the fulldisttree.- The launcher renders but the iframe is blank: the iframe
srcis the bundle's directory, so make sureindex.htmland assets exist there. Then check the browser console for aREACT_APP_API_URLbaked at build time that points at localhost. - The iframe refuses to load:
X-Frame-Options: SAMEORIGIN/DENYor CSPframe-ancestorson the widgets host (see the callout above), or CSP on your site blocking the frame/script. - "Integration not found" or the widget never connects:
integrationIddoes not match a messenger-kind integration in this tenant (widgetsGetMessengerIntegration/widgetsMessengerConnectfail). 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, orALLOWED_ORIGINSlist. - Messages send but replies never arrive: the
graphql-wssubscription onwss://<gateway>/graphqlis 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.jsreturns 404:frontline-widgetsdoes not build it. Use the messenger FAQ orapps/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.