The script-tag flavour is the lowest-friction way to ship the widget. It works on plain HTML, WordPress, Webflow, Shopify, or any CMS that lets you paste <script> into a template.
The bundle
The library bundle (appmint-chat.js) and its CSS (main.css) are hosted on the AppMint CDN. The canonical URLs are:
https://web.appmint.space/chat-client/appmint-chat.js
https://web.appmint.space/chat-client/static/css/main.css
The bundle is a UMD build (output by react-scripts build plus update-script-hashes.js) that exposes a global AppmintChat object with a single init method.
The CDN URL stays stable. Internally, the post-build script update-script-hashes.js copies the hashed Create-React-App output into main.js so the same URL always points at the latest build. There is no version pinning at the URL level — every page running the widget pulls the current build.
Minimum markup
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://web.appmint.space/chat-client/static/css/main.css">
</head>
<body>
<div id="my-chat-container"></div>
<script>
function initializeChat() {
AppmintChat.init({
containerId: 'my-chat-container',
orgId: 'your-org-id',
configId: 'your-config-id',
appId: 'chat-client',
theme: 'light',
language: 'en'
});
}
</script>
<script
src="https://web.appmint.space/chat-client/appmint-chat.js"
onload="initializeChat()"
></script>
</body>
</html>
Customisation via globals
Because the bundle is loaded as a single global, configuration happens through the init call. There's no per-page data- attribute API — pass everything through the config object.
For per-page overrides (e.g., different language on a Spanish landing page) read the value at runtime and forward it:
<script>
function initializeChat() {
const lang = document.documentElement.lang || 'en';
AppmintChat.init({
containerId: 'my-chat-container',
orgId: 'your-org-id',
configId: 'your-config-id',
appId: 'chat-client',
language: lang,
theme: window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
});
}
</script>
Identifying a known visitor
If your CMS already knows who the visitor is, pass identity through init:
<script>
function initializeChat() {
AppmintChat.init({
containerId: 'my-chat-container',
orgId: 'your-org-id',
configId: 'your-config-id',
appId: 'storefront',
user: {
email: '<%= visitor.email %>',
firstName: '<%= visitor.firstName %>',
lastName: '<%= visitor.lastName %>'
},
token: '<%= visitor.chatToken %>'
});
}
</script>
Tokens shipped to the page are visible to anyone who views source — only mint short-lived chat-scoped JWTs and never reuse your AppEngine API key here.
Single-page apps without a build
Some templates re-render parts of the DOM as the visitor navigates. The widget is unaffected — it mounts a fixed-position root inside #my-chat-container and lives outside any client-side router. As long as the container element survives navigation, the widget stays alive.
If your app destroys the container, you'll need to call AppmintChat.init(...) again on each route change.
Self-hosting the bundle
If you'd rather host the bundle yourself:
- Clone
chat-client/, runyarn install, thenyarn buildto producebuild/. - Run
node update-script-hashes.jsso the hashed CRA output is also available asmain.js. - Sync
build/to your CDN. The repo'sdeploy.jsdoes this against a DigitalOcean Space — adapt for S3, Cloudflare R2, etc. - Replace the
https://web.appmint.space/chat-client/...URLs with your own.
The bundle has no runtime dependency on the AppMint CDN — only on AppEngine for chat config and the socket connection. Your origin can serve the static files freely.
Troubleshooting
- Bubble doesn't appear — check the browser console. The most common error is a 404 or CORS error on the chat config fetch (verify
orgIdandconfigId). - "AppmintChat is not defined" —
initializeChatran before the script loaded. Wire it viaonloadas shown above, or wrap the call inDOMContentLoadedafter the script tag. - Styles look broken — the
main.csslink is missing or 404'd. The CSS is required for the layout, even though the bundle inlines its own component CSS.