The AppMint chat client is a self-contained chat panel — Socket.IO client, message UI, presence, file upload, AI streaming — that drops onto any web page. AppEngine's ChatGateway is the other end. This tutorial gets the widget rendering and talking to AppEngine in three configurations: Next.js client-side, Next.js server component, and a plain HTML page.
There is no @appmint/chat package on npm. The bundle ships from a CDN URL (https://web.appmint.space/chat-client/appmint-chat.js) and exposes a single global, window.AppmintChat. Every install path on this page is a small wrapper around a <script> tag.
What you need
- AppEngine instance with the chat module enabled (default for new orgs)
- A chat config record (org → admin → Chat → Configurations → New). The id of that config is one of the props you'll pass to the widget.
- Optional: an AI assistant attached to the chat config so visitor messages route to a Claude/GPT-style agent instead of a human.
Three install paths
| Stack | Install path |
|---|---|
| React or Next.js client component | A small 'use client' wrapper that injects the script tag and calls window.AppmintChat.init |
| Next.js App Router server component | A server component that reads env vars on the server and forwards them to the client wrapper |
| Static HTML, Webflow, WordPress | <script> tag pointing at the bundle, then call AppmintChat.init(...) inline |
Prerequisites
- An
orgId,configId, and (optional)appIdfrom AppEngine - For Next.js: the
NEXT_PUBLIC_APPENGINE_URLenv var pointing at your AppEngine host
Step-by-step
- 1
Create the chat config
In the admin UI, Chat → Configurations → New. Fill out:
- Name — internal label
- Welcome message — what the visitor sees when they open the panel
- Theme — light/dark/auto + accent color
- Routing — to a queue of human agents, an AI assistant, or both with handoff
- Availability — business hours, off-hours auto-reply
- Pre-chat form — required fields before chat starts (name, email, ticket subject)
Save. Copy the
configIdfrom the detail page. You'll pass it as a prop. - 2
Add the React wrapper component
Copy the canonical wrapper from yugo (
/Users/imzee/projects/yugo/src/components/AppmintChat.tsx). It's a'use client'component that injects the CDN script and callswindow.AppmintChat.initon mount:// src/components/AppmintChat.tsx 'use client'; import { useEffect } from 'react'; const AppmintChatComponent = ({ orgId, configId, appId, className = '', style = {}, theme = 'light', language = 'en', containerId = 'appmint-chat-container', defaultPath = '', }: any) => { useEffect(() => { const init = () => { (window as any).AppmintChat?.init({ containerId, orgId, configId, appId, className, style, defaultPath, theme, language, }); }; const existing = document.getElementById('appmint-chat-script'); if (!existing) { const s = document.createElement('script'); s.id = 'appmint-chat-script'; s.src = 'https://web.appmint.space/chat-client/appmint-chat.js'; s.async = true; s.onload = init; document.body.appendChild(s); } else { init(); } }, [orgId, configId, appId, theme, language, containerId]); return <div id={containerId} className="w-full h-full" />; }; export default function AppmintChat(props: any) { if (!props.configId || !props.appId) return null; return <AppmintChatComponent {...props} />; }Mount it in the root layout so the bubble is on every page:
// src/app/layout.tsx import AppmintChat from '@/components/AppmintChat'; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <body> {children} <AppmintChat orgId={process.env.NEXT_PUBLIC_ORG_ID!} configId={process.env.NEXT_PUBLIC_CHAT_CONFIG_ID!} appId="my-app" /> </body> </html> ); }The widget renders a floating bubble bottom-right by default. Click → panel opens → socket connects to AppEngine → conversation starts.
- 3
(Optional) Add a server-component wrapper
If you'd rather read non-public env vars on the server, add a thin server component that forwards them to the client wrapper. Same pattern yugo uses (
/Users/imzee/projects/yugo/src/components/AppmintChatServer.tsx):// src/components/AppmintChatServer.tsx import AppmintChat from './AppmintChat'; export default function AppmintChatServer() { const configId = process.env.chatConfigId; const appId = process.env.chatAppId; const orgId = process.env.NEXT_PUBLIC_ORG_ID; return <AppmintChat configId={configId} appId={appId} orgId={orgId} />; }Drop it in your root layout:
// src/app/layout.tsx import AppmintChatServer from '@/components/AppmintChatServer'; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html><body>{children}<AppmintChatServer /></body></html> ); }This keeps non-
NEXT_PUBLIC_env names off the browser bundle. The browser still does the actual script-tag injection on mount inside the innerAppmintChatclient component. - 4
Install on a plain HTML page
Use the CDN-hosted bundle. A container div, the script, and an init call:
<!-- somewhere on the page --> <div id="appmint-chat-container"></div> <!-- before </body> --> <script src="https://web.appmint.space/chat-client/appmint-chat.js" onload="initChat()" ></script> <script> function initChat() { AppmintChat.init({ containerId: 'appmint-chat-container', orgId: 'your-org-id', configId: 'your-config-id', appId: 'website', theme: 'light', defaultPath: '/chat', mode: 'chat', }); } </script>The
modeprop picks between full chat (chat) and the knowledge-base Q&A flavour (qa). For a marketing site,chatis what you want;qais for support pages where the visitor types a question and gets an instant answer from indexed docs. - 5
Authenticate the visitor (optional)
Out of the box the widget creates an anonymous guest record. To attach the chat to a known customer, extend the wrapper to forward the customer's identity and chat-scoped JWT into
init:// In your AppmintChat wrapper, inside the useEffect: (window as any).AppmintChat?.init({ containerId, orgId, configId, appId, user: { id: customer.id, email: customer.email, name: customer.name }, token: customerToken, });When the token is present, the chat opens in the customer's history (any prior conversations show up), and AppEngine attaches messages to their customer record. Don't pass a token for unauthenticated visitors — let the widget create the guest itself.
- 6
Wire up AI handoff
If your chat config has an AI assistant attached, the widget routes opening messages to it. To toggle handoff to a human:
- Visitor types
"talk to a human"(configurable phrase) — AppEngine assigns a human agent if one is available, else queues. - Agent in the admin chat console clicks Take over — same effect.
- AI itself can hand off via a tool call (
request_human_agent) — useful when the AI hits its competence boundary.
All three paths flip the conversation's
assigneefield. The widget doesn't need to know which mode it's in; messages flow either way.For more on the AI side, see Chat AI assistants.
- Visitor types
- 7
Customize appearance
initaccepts athemevalue — either a string ('light'/'dark') or an object with token overrides:(window as any).AppmintChat?.init({ containerId, orgId, configId, appId, theme: { primaryColor: '#3b82f6', backgroundColor: '#ffffff', textColor: '#1f2937', borderRadius: '8px', fontFamily: 'Inter, sans-serif', }, });For deeper customization (replace the bubble icon, override the welcome screen, add a custom pre-chat form), edit the chat config in AppEngine — changes propagate without a redeploy. The widget reads its config on mount.
- 8
Lock messaging to specific pages
If you only want the bubble on
/supportand/pricing, conditionally render. The widget cleans up its socket on unmount.'use client'; import { usePathname } from 'next/navigation'; import AppmintChat from './AppmintChat'; export function ConditionalChat() { const pathname = usePathname(); const showChat = ['/support', '/pricing'].some((p) => pathname.startsWith(p)); if (!showChat) return null; return <AppmintChat orgId={...} configId={...} />; }
Wire protocol notes
Once the widget is up, useful AppEngine endpoints to know:
/chat/configNo authUsed by the widget on boot to fetch the config you created in step 1.
/chat/guestNo authMints a guest token if no auth was provided. Call site is internal to the widget.
/chat (Socket.IO namespace)JWTReal-time message channel. Events: message, typing, presence, assignment. See Chat WebSocket protocol for the full event list.
Troubleshooting
- Bubble appears but never connects. Check
NEXT_PUBLIC_APPENGINE_URLis reachable from the browser (CORS, mixed content). The widget logs to the console with prefix[appmint-chat]. - Welcome message doesn't appear. The chat config might not be published. Open it in the admin UI → state should be
active. - AI replies but messages don't persist. Check the chat config's "Persist anonymous conversations" toggle. By default, anonymous conversations are kept for 24h then garbage-collected.
What's next
- Chat client overview — the full widget reference.
- Chat module — the AppEngine side.
- Add AI features — for an AI experience without the bubble UI.