Web widget
Updated 2026-10-06
One line on your site and a chat bubble shows up in the bottom-right corner: your visitors talk to your agent, no account needed.
Turn it on
From Agents → your agent → Channels → Web widget: flip the switch, list your domains and copy the code. Or from the terminal:
It gives you the code to paste before </body>:
It works the same on hand-written HTML, WordPress, Shopify, Wix or any site that lets you paste a <script>.
Where it loads: your domains
The widget only opens on the domains you list. If someone copies your code to another site, the browser won't load it.
mystore.comalso coverswww.mystore.com.*.mystore.comcovers any subdomain (blog.mystore.com,shop.mystore.com).- With no domains it loads nowhere.
httpsonly.
What a visitor can do
- Only chat with your agent. Each visitor has their own conversation and can't see anyone else's; the agent runs isolated per visitor.
- They talk as a customer: the agent reads your databases and documents per what you granted the channel, but doesn't administer (no boards, email or databases without write permission).
- The conversation is kept in their browser and picks up where they left off.
- Every message lands in your CRM as a web-channel conversation.
Customize it
Everything changes in Channels → Web widget, by API (PATCH /api/v2/agents/{id}/embed) or from the terminal. The code you already pasted doesn't change: the bubble fetches its look when it loads (a change can take up to a minute to show).
The avatar is copied to img.ghosty.studio, so the widget keeps working if your image moves.
Fine style
To make it look exactly like your site, the style block changes the typeface, the corners and the color of each piece. It takes no free CSS: only these keys, each one validated. A key set to null goes back to its default; --style none resets them all.
Key | What it changes | Looks like |
|---|---|---|
fontFamily | A Google Font from an allow-list: Inter, Barlow, Barlow Condensed, Poppins, Montserrat, Roboto, Open Sans, Lato, Nunito, DM Sans, Work Sans, Manrope, Rubik, IBM Plex Sans, Source Sans 3, Raleway, Merriweather, Lora, Playfair Display, Libre Baskerville, Cormorant Garamond, Space Grotesk, Source Serif 4 | AaAaAa |
radius | Corners of the panel, bubbles, box and send button (0–24 px; 0 = square) | |
headerBg · headerText | Header background and text | Asistente |
titleUppercase | Title in UPPERCASE with letter spacing | Asistente |
statusDot | Status dot next to the title (a color) | Asistente |
headerAvatar | false hides the avatar in the header | |
panelBg | Panel background | |
userBubbleBg · userBubbleText | Visitor bubble | Do you ship? |
agentBubbleBg · agentBubbleText | Agent bubble (without it, the agent writes with no bubble) | Sure! |
inputBg | Message box | Type… |
sendBg · sendIcon · sendArrow | Send button, its arrow and its direction ( up or right) | ↑→ |
launcherBg · launcherIcon | Floating button (always round) | |
typing · typingColor | The "typing" dots while the agent replies: bounce (the default), flow (wave), flashing (blink) or shimmer (glowing "Thinking…" text, no dots), and their color (without it, your brand color) |
Colors are #rrggbb. A text color you leave out is computed by contrast against its background.
Example: Palmera Legal style — dark green header with the title in uppercase, gold dot, square corners everywhere, green and beige bubbles, Barlow, and gold typing dots in a wave:
--font and --radius are shortcuts for style.fontFamily and style.radius.
Typing dots
While the agent replies, the chat shows three dots. By default they take your brand color (the header's). --typing-color gives them another one and --typing changes the animation:
typing | What it does | Looks like |
|---|---|---|
bounce | They hop one after another (the default) | |
flow | They grow in a wave | |
flashing | They blink, like WhatsApp | |
shimmer | No dots: the "Thinking…" text with a glow running across it | Pensando |
--typing-color none goes back to the brand color. In the agent's page it lives under Channels → Web widget → Advanced.
Where the theme comes from
In the card you pick New (your values), From Teams or From my site, with a preview:
- From Teams: takes the active brand of your Ghosty Teams workspace (Settings → Brand): primary goes to the header, the button and the visitor bubble; accent to the send button and the status dot; surface to the background; and its typeface if it's on the list.
- From my site: reads the colors and typeface from your page's CSS (not from images). If your site paints everything with JavaScript, it tells you it found no colors.
Both are imported as editable values: then tweak whatever you want in Advanced → Style. Apply them again to refresh.
Texts while it works
While the agent thinks, the widget shows a few phrases in Ghosty's voice («Me estoy poniendo los lentes 👓», «Estoy abriendo mi libreta», «Estoy pensando»…). If they don't fit your business, change them in the card (Advanced → Texts, with a preview), through the API (copy) or with the CLI. Whatever you don't change keeps Ghosty's phrase.
Plain one-line text, up to 80 characters (no < or >). It's a patch per key: null brings that phrase back to Ghosty's and --copy none brings all of them back. An unknown key or a long text answers 400 with the reason.
Example, a law firm («Palmera Legal»):
agents embed <id> theme exports copy too, and --style-file applies it back.
React
If your site is React (Next.js, Remix, Vite…), you have three paths. All three talk to the same agent and honor your domains and caps.
ghosty agents embed <agent-id> prints this snippet with your token already in it (and in --json, under react).
The bubble: <GhostyWidget>
It mounts and cleans up with the component. It takes the saved look and whatever you pass as props wins: position, offsetX, offsetY, bubbleText, defaultOpen, open + onOpenChange (controlled), style with the same keys as (including the inside ones: bubbles, input and send, validated the same way as when saved) and copy with the ones in (copy={{ thinking: ["Reviewing your question…"] }}).
Your own chat: useGhostyChat
messages:{ id, role: "user" | "assistant", text, pending? }; the agent's text streams in live.status:connecting·idle·thinking(sent, no text yet) ·streaming.error: caps and notices arrive as text ready to show.copy: your (complete, with Ghosty's where you changed nothing) to draw your own waiting state:copy?.thinking[0].nulluntil they arrive.- On return,
messagesbrings back the visitor's previous conversation (one id per browser inlocalStorage, keyghosty:widget:vid).
Your own component: --out
If you'd rather not depend on a package (shadcn style), the CLI writes a complete, editable component into your project, with your token and current theme (colors, font, corners, title, placeholder, welcome, suggestions) as constants at the top of the file, and comments saying where to change each thing:
It won't overwrite an existing file without --force. It ships useGhostyChat() (the logic) and GhostyChat (the UI) separately: change the UI without touching the connection.
Domains and CORS
From your page, headless and component requests go straight to Ghosty: they only pass if the Origin is one of your widget domains (same rules: yourstore.com includes www., *.yourstore.com its subdomains, https only). Any other site gets 403 and the browser blocks the response. It doesn't work on localhost: test on an authorized domain (or use <GhostyWidget>, which lives in the iframe).
Caps and cost
Turns are billed to your account, like any other channel. So nobody burns them:
If your plan or key runs out, the visitor sees "not available right now" and you see the reason with ghosty agents embed <agent-id>.
Change it or turn it off
Requirements
- Claude or MiniGhosty (ghosty-lite) engine: they isolate each visitor. It can't be turned on with another engine.
EasyBits agents
Agents created under Agents → EasyBits keep their usual widget: an <iframe> with their embed token.