Skip to main content
@custral/widget is the Custral chat widget as a React component, plus the headless engine underneath it. It uses a publishable key (pk_…), which is safe to ship to the browser.
No bundler, or not a React app? Load the standalone widget.js script instead. It is this same package: a 7 KB script that draws the launcher, and the chat itself, built on Preact (a small stand-in for React), which loads when a visitor reaches for it.
This package was private (@repo/widget) until the browser packages were reorganised, and Settings once generated an install snippet naming it. That name was never on npm. Use @custral/widget.

Install

React and React DOM are peer dependencies (18 or 19), so the widget renders on your copy of React rather than bundling a second one.

The widget

One component, mounted once, anywhere in your tree. It renders its own bubble and panel and positions itself.
string
required
Your publishable key (pk_…), from Settings → Applications.
string
Target a specific widget channel. Defaults to the workspace’s first.
string
Defaults to https://api.custral.com.
boolean
Force dark or light. Omitted, the widget uses the theme set in Settings → Chat Widget, where Auto reads the host page.
{name?, email?, phone?}
Pre-fill who the visitor is. Skips the intake form. Self-reported, so it only fills empty fields on the visitor’s record.
string
A token minted by your server that vouches for who this visitor is. See below.

Identifying a visitor

visitor is convenient and self-reported: it fills the form in, and the person on the other end typed it. That is fine for lead capture and not fine for anything that has to be right. identityToken is the version the product will trust. Your server mints it with its secret key, so the claim comes from you rather than from the browser:
Never mint an identity token in the browser. It authenticates with your secret key, and anything that can call it can vouch for any of your users.
Without a token, a visitor’s identity is only ever what they typed into a chat box on a public page. With one, the conversation attaches to the right person in your CRM. See widget identity.

Signing a visitor out

The widget remembers its visitor in the browser between page loads. On a shared computer, the next person to use your app would resume the previous visitor’s conversations, unless that visitor was signed in with an identityToken: the widget never reopens those without the same person’s token. Call resetCustralChat() when someone signs out:
It forgets this browser’s visitor and restarts every mounted widget as a new, anonymous visitor. A restarted widget ignores the identityToken and visitor it was rendered with until you pass a different token, so the person who just left is not signed straight back in. Requires @custral/widget 1.0.1 or later.

Building your own UI

The component is assembled from parts the package also exports, so you can replace the shell and keep the machinery.
useCustralChat owns the conversation: init, socket, messages, typing, intake, conversation list, booking. ChatBubble, ChatPanel, ChatHeader, ChatMessageList, ChatMessage, ChatInput, IntakeForm, ConversationList and SchedulePicker are the pieces it drives. For no React at all, CustralChat is the engine on its own:
This CustralChat is the engine class. The window.CustralChat that the widget.js script adds is a different object: the page API with open(), hide() and events. See Control the chat from your page. The React component doesn’t add it, and doesn’t read data-custral-open.
Construct a headless client.
Loads widget config, restores any prior visitor from localStorage, and returns the resolved config (or null on failure).
Registers a callback for agent messages pushed in real time.
Sends a visitor message, creating a conversation on the first send.
Attaches self-reported contact info for lead capture.
Tears down the socket and in-memory session. By default the browser keeps its visitor, so the next init() resumes it. Pass {clearSession: true} when a user signs out, so the next person on this browser starts as a new visitor.

Troubleshooting

The widget needs a publishable key (pk_…), not a secret key. Create one in Settings → Applications. If init() returns null, the /widget/init call failed: check the browser console and confirm the key and channelId.
Delivery uses a WebSocket to /widget. If your environment blocks WebSockets, history still loads on demand, but pushed agent replies will not arrive. Look for Socket connected in the console.
Two copies of React in one page. React and React DOM are peers here for that reason; check your bundler is deduping them.
CustralChat and the components are all in @custral/widget now. Change the import and nothing else. @custral/ui 2.0 is a different package, the React data SDK.