Documentation

Embed the chat widget

Drop the AppMint chat-client into a Next.js or static site and connect it to AppEngine's chat gateway.

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.

No npm package

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

StackInstall path
React or Next.js client componentA small 'use client' wrapper that injects the script tag and calls window.AppmintChat.init
Next.js App Router server componentA 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) appId from AppEngine
  • For Next.js: the NEXT_PUBLIC_APPENGINE_URL env var pointing at your AppEngine host

Step-by-step

  1. 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 configId from the detail page. You'll pass it as a prop.

  2. 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 calls window.AppmintChat.init on 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. 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 inner AppmintChat client component.

  4. 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 mode prop picks between full chat (chat) and the knowledge-base Q&A flavour (qa). For a marketing site, chat is what you want; qa is for support pages where the visitor types a question and gets an instant answer from indexed docs.

  5. 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. 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 assignee field. 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.

  7. 7

    Customize appearance

    init accepts a theme value — 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. 8

    Lock messaging to specific pages

    If you only want the bubble on /support and /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:

GET/chat/configNo auth

Used by the widget on boot to fetch the config you created in step 1.

POST/chat/guestNo auth

Mints a guest token if no auth was provided. Call site is internal to the widget.

WS/chat (Socket.IO namespace)JWT

Real-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_URL is 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