Installation Guides
The hosted widget.js integration is the no-build path when you want ChattyBox to maintain the interface and transport. If your app should initialize the same UI from npm code, use mountWidget(). To own the UI, use the headless SDK.
Before Installing
Complete the Getting Started flow first: configure and scrape the source, review indexed pages, and verify representative answers in Test Chat.
Then create a browser-safe key in Public Keys. It works across production, preview/staging, and localhost origins by default. To add optional defense-in-depth hardening, choose Edit origins, enable Restrict this key to specific origins, and add the exact permitted origins. When enabled, matching includes scheme, hostname, and port: https://example.com, https://preview.example.com, and http://localhost:3000 are separate entries. Return to Embed, select the key, and copy the generated snippet. It includes the public key and API URL for your project:
<script
src="https://chattybox.ai/widget.js"
data-api-key="YOUR_API_KEY"
data-api-url="YOUR_WIDGET_API_URL"
async
></script>
For docs-oriented platforms, see the MkDocs AI chatbot, VitePress AI chatbot, and GitBook AI chatbot guides.
Replace YOUR_API_KEY with the public widget key from your dashboard. Keep the data-api-url value exactly as shown in the dashboard. In production, this is a stable https://...convex.site/chat URL for the public widget API.
What Belongs in the Script
Use script attributes for values that must be available before the widget can start:
| Attribute | Required | Use it for |
|---|---|---|
src | Yes | Loading the ChattyBox widget JavaScript. |
data-api-key | Yes | Identifying the public widget key for your project. |
data-api-url | Yes | Sending widget requests to the ChattyBox API. |
data-locale | No | Requesting a UI language at initialization, only when project script overrides are allowed. |
data-chattybox-widget="true" | No | Lets dynamic loaders and the SDK find an existing script. Recommended when injecting from application code. |
data-debug="true" | No | Enabling browser diagnostics; off by default. |
data-color | No | Explicit accent-color override after a successful config fetch; use a six-digit hex color. Prefer the dashboard. |
data-position | No | Explicit position override after a successful config fetch: bottom-right, bottom-left, top-right, or top-left. Prefer the dashboard. |
Use dashboard settings for everything that should be managed without redeploying your site:
- Widget colors, position, icon, title, and welcome message.
- Default language mode and whether
data-localeoverrides are allowed. - Public key creation, deletion, and optional per-key origin restrictions. Open Public Keys > Edit origins to enable a restriction and add or remove production, staging, preview, or localhost origins.
- Scraping, re-scraping, test chat, analytics, and content gaps.
If you enable the config-as-code lock, assistant, source, runtime, and supported widget settings come from the deployed config instead of dashboard forms. Public keys and optional origin restrictions remain project setup settings, not config-file values.
The API URL can be the HTTP deployment root or end in /chat; the loader strips trailing slashes and a trailing /chat before deriving its endpoints. It does not convert a Convex .cloud URL into .site. Keep query strings and fragments out of this value.
Install one loader only. The SDK recognizes the marker attribute above, not every manual script with the same src. Published SDK 0.1.4 reference-counts only identical mount options and rejects a changed key, endpoint, script URL, locale, or debug setting until all handles are removed. The widget loads configuration and translations once at initialization; it does not subscribe to dashboard changes or changes to page language. Reload to pick up saved settings. Origin restrictions, when enabled, use Origin or a fallback Referer origin; absent or disallowed origins produce 401 Invalid API key. They are not authentication.
Plain HTML
Paste the snippet once near the end of body, just before </body>. This works for static HTML, hand-coded sites, and templates that expose a global footer.
<!doctype html>
<html lang="en">
<head>
<title>Example Site</title>
</head>
<body>
<main>
<!-- Page content -->
</main>
<script
src="https://chattybox.ai/widget.js"
data-api-key="YOUR_API_KEY"
data-api-url="YOUR_WIDGET_API_URL"
async
></script>
</body>
</html>
Next.js / React App Shell
For a Next.js App Router site, add the widget to app/layout.tsx with next/script so it loads once for the whole app.
import Script from "next/script";
import type { ReactNode } from "react";
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body>
{children}
<Script
src="https://chattybox.ai/widget.js"
data-api-key="YOUR_API_KEY"
data-api-url="YOUR_WIDGET_API_URL"
data-chattybox-widget="true"
strategy="afterInteractive"
/>
</body>
</html>
);
}
For a React single-page app, add the script once in your top-level app shell or HTML template. Do not inject it from every route component.
Keep the loader in a persistent shell during ordinary navigation. For route-scoped removal or consent revocation, remove every SDK 0.1.4 handle: the final matching handle signals hosted widget.js v15 to destroy its owned UI/styles and cancel pending initialization, retries (up to 10 attempts within 30 seconds), and in-flight chat; window.ChattyBox.destroy() is available after v15 initialization. Cleanup cannot undo a server-processed request. The hosted widget floats under document.body; it does not render inside the component that loads it.
import { useEffect } from "react";
export function ChattyBoxWidget() {
useEffect(() => {
if (document.getElementById("chattybox-widget-script")) return;
const script = document.createElement("script");
script.id = "chattybox-widget-script";
script.src = "https://chattybox.ai/widget.js";
script.async = true;
script.setAttribute("data-api-key", "YOUR_API_KEY");
script.setAttribute("data-api-url", "YOUR_WIDGET_API_URL");
script.setAttribute("data-chattybox-widget", "true");
document.body.appendChild(script);
}, []);
return null;
}
Docusaurus
For Docusaurus, create or update src/theme/Root.tsx so the widget is available across docs pages.
import React, { useEffect } from "react";
export default function Root({ children }: { children: React.ReactNode }) {
useEffect(() => {
if (document.getElementById("chattybox-widget-script")) return;
const script = document.createElement("script");
script.id = "chattybox-widget-script";
script.src = "https://chattybox.ai/widget.js";
script.async = true;
script.setAttribute("data-api-key", "YOUR_API_KEY");
script.setAttribute("data-api-url", "YOUR_WIDGET_API_URL");
script.setAttribute("data-chattybox-widget", "true");
document.body.appendChild(script);
}, []);
return <>{children}</>;
}
If your Docusaurus site has translated routes, set data-locale before loading (when overrides are allowed), or use auto mode and the page's <html lang>. Fixed mode uses the project's default locale unless an allowed script override is present. The widget resolves locale only at initialization, not on client-side route changes; a full page reload picks up the new page language.
Keep the loader in your persistent application shell. Do not recreate it during normal client-side route changes; use v15 destruction only when you intentionally need teardown and have SDK 0.1.4 installed.
Custom Interface
The hosted widget is optional. If you want full control over rendering, message state, and interaction design, use the JavaScript SDK with the same public widget API key and widget API URL.
Generic CMS/Custom HTML
Most CMS platforms have a global custom code, footer, or theme template area. Add the script there so every public page can load the widget.
Use this path for Webflow, Framer, Squarespace, Wix custom code areas, Shopify themes, HubSpot templates, and custom CMS platforms that let you edit global HTML.
<script
src="https://chattybox.ai/widget.js"
data-api-key="YOUR_API_KEY"
data-api-url="YOUR_WIDGET_API_URL"
async
></script>
Before publishing, confirm the CMS does not strip data-api-key, data-api-url, or async from custom scripts.
Google Tag Manager
Use Google Tag Manager when your team already manages third-party scripts through GTM.
- Open your GTM container.
- Create a new Custom HTML tag.
- Paste the ChattyBox snippet.
- Use an All Pages trigger, or a narrower trigger for only the pages that should show the widget.
- Preview the container, verify the widget loads, then publish.
<script
src="https://chattybox.ai/widget.js"
data-api-key="YOUR_API_KEY"
data-api-url="YOUR_WIDGET_API_URL"
async
></script>
If your site uses consent mode or a tag consent policy, ensure the widget is allowed to load on the pages where visitors need help.
On SPAs, a narrower GTM trigger prevents the initial load on excluded routes but does not remove a widget already loaded on another route. Do not repeatedly fire the loader on history changes.
Content Security Policy
For the default hosted loader, permit https://chattybox.ai in script-src and your widget API origin in connect-src. The widget injects a stylesheet and inline style attributes; it currently has no nonce option, so a policy forbidding inline styles will block parts of its UI. If your policy permits it, allow 'unsafe-inline' for these styles, or use a custom UI compatible with your policy. Loading Inter on launcher interaction also requires https://fonts.googleapis.com in style-src and https://fonts.gstatic.com in font-src. Allow any configured icon image origin in img-src. Review consent and security requirements before relaxing CSP.
WordPress plugin guide
For a no-code WordPress installation, use the ChattyBox WordPress plugin. Follow the WordPress plugin guide to install, configure, exclude routes, and verify it. The production API endpoint is configured automatically.
The plugin loads the hosted widget on public frontend requests without editing your theme. It intentionally does not load in wp-admin, feeds, REST requests, or AJAX requests.
This is not a privacy/access check for every frontend page: exclude checkout, account, password-protected, or membership pages explicitly when needed. The plugin's loader URL setting does not change its built-in production API endpoint; use the manual snippet for a different API deployment.
If you prefer a manual script install, use one of the script locations your WordPress setup already supports:
- Theme settings that provide header or footer scripts.
- A child theme that controls the footer template.
- A header/footer script plugin.
- Google Tag Manager if your WordPress site already uses it.
Paste the snippet in a global footer location so it appears on published pages, posts, docs, and knowledge base articles where the chatbot should be available.
<script
src="https://chattybox.ai/widget.js"
data-api-key="YOUR_API_KEY"
data-api-url="YOUR_WIDGET_API_URL"
async
></script>
Avoid adding the widget to wp-admin, checkout, account, or private membership pages unless those pages are intentionally public and supported.
Drupal module
For Drupal 10 or 11, Composer is the recommended installation path. Add the public GitHub VCS repository to the consuming Drupal project's root composer.json:
{
"repositories": {
"chattybox-drupal": {
"type": "vcs",
"url": "https://github.com/OpenStaticFish/chattybox-drupal.git"
}
}
}
Then install the tagged module and enable it:
composer require openstaticfish/chattybox-drupal:^0.1
drush en chattybox
Open Configuration > Web services > ChattyBox, paste the public widget API key from the project Embed tab, and enable the chatbot. The production API endpoint is configured automatically. See the Drupal chatbot module guide for route exclusions and the manual fallback.
The module starts disabled. It skips administrative and non-HTML requests, not all private frontend content. Changing its script URL does not change the built-in production API endpoint. Use a manual widget snippet instead of the module for a different API deployment.
Verification
After installing, run the launch checklist before announcing the chatbot:
- Open a public page in an incognito window.
- Confirm the widget launcher appears.
- Open the widget and ask a real customer question.
- Verify a supported answer includes relevant source citations, then ask an unsupported question and check the fallback (which can have no sources).
- Check the browser console for missing
data-api-key, missingdata-api-url, or key errors. Check origins only if you explicitly enabled a restriction for the key.