Install
One line of HTML, once per page that should show the chat. Put it before the closing
</body> tag. Your channel's own line — with its key already in it — is
in the dashboard under Web Chat Channels → your channel → Overview.
<script src="https://widget.fellix.ai/widget.js" data-key="wc_pk_…" async></script>
async is deliberate: nothing on your page waits for us. The script draws a
launcher button and nothing else. The conversation itself lives in an iframe on Fellix's
own origin, and that iframe is not created until the visitor opens the panel — so a
visitor who never clicks pays for one small script and no more.
The boundary is the point. None of our React, CSS or network traffic runs in your document. Your stylesheet cannot reach into the panel, and ours cannot leak out onto your page.
Snippet attributes
All optional except data-key. Each is read once, from the script tag itself,
at the moment the script runs — changing an attribute later has no effect, because by
then the tag is no longer identifiable.
data-key required
This channel's public embed key. Without it the widget does not start: it logs
[fellix] the embed snippet is missing its data-key attribute to the console
and returns, drawing nothing. If you pasted the snippet and no launcher appeared, that
console line is the first place to look.
The key is publishable by design — it identifies the channel, it does not authorise anything. Which websites may use it is controlled separately, under the channel's Security settings.
data-lang
What language the widget speaks.
- absent, or
page -
Follow the page's own
<html lang>, falling back to the visitor's browser when the page declares none. This is the default, and usually the right answer: the widget is furniture on your page, and an English-only site should not show a Turkish composer under English content just because the visitor's browser is set to Turkish. browser- Follow the visitor's own browser preference instead of the page.
- a language tag —
tr,de,pt-BR - Pin it, whatever the page and the visitor say.
Only the first two letters are used to pick the wording, so pt-BR and
pt reach the same translation. The widget ships English, Turkish, German,
Spanish, French, Arabic and Russian; anything else falls back to English. Arabic, Hebrew,
Persian and Urdu also flip the panel to right-to-left.
data-theme
Whether the panel is painted light or dark.
- absent, or
page -
Follow the page. We read the computed
color-schemeof your<html>element — not a class name, because.darkis Tailwind's convention anddata-bs-themeis Bootstrap's and neither is recognisable on a stranger's site.color-schemeis the one answer every framework produces, because the browser itself needs it to paint the canvas and the form controls. os- Ignore the page and follow the visitor's own system setting.
light/dark- Pin it.
The channel outranks this. If the channel's Panel theme is set to
Light or Dark in the dashboard, that wins and data-theme is not consulted at
all — a page is not entitled to overrule the brand it embedded. This attribute only does
something while Panel theme is on System.
data-position
Which corner the launcher sits in.
- absent
- The channel's Bubble position setting decides, as it does for every site the key is pasted into. That setting has a Follow the visitor's language switch of its own, so leaving this attribute off does not necessarily mean a fixed corner — it means whichever of the two the dashboard is set to give.
left/right- Pin it for this site.
auto- Follow the widget's own language: right on a left-to-right page, left on a right-to-left one. What a site that mirrors itself for Arabic wants.
Unlike data-theme, the page wins here. The setting
is one answer for everywhere the key is used; this attribute is one site talking about its
own corners, where a cookie bar or a back-to-top button may already be.
data-label
Replaces the text a screen reader announces for the launcher button. Defaults to a translated “Chat with us”. Set it when “chat” is not what the button means on your site — “Book a table”, “Ask about your order”. It is an accessible name, not visible copy; the visible label is the channel's Bubble label setting.
data-jwt
A signed identity token for the person reading the page, when your backend renders the
snippet server-side and already knows who they are. A single-page application that learns
who its user is only after a login calls
fellixWidget.identify() instead. See
Signed identity.
data-panel
The origin the panel iframe is loaded from. Defaults to the origin of the script's own
src, which is correct for every ordinary installation — you will not normally
set this. It exists for environments that serve widget.js from a different
host than the panel, such as a reverse proxy in front of both.
Leave it off rather than empty. data-panel="" is not the
same as omitting it: an empty value is still a value, so the fallback to the script's own
origin never happens and the panel is loaded from nowhere. Templating engines that emit
every attribute whether or not they have something to put in it are the usual way this
happens.
Opening it from your own page
Two ways in that need no JavaScript of yours. Both work alongside the launcher, and both still work when the launcher is hidden — the channel's Use my own button setting turns the bubble off entirely and leaves these as the way in.
[data-fellix-open]
Put the attribute on any element — a button, a link, a menu item — and clicking it opens the panel. An attribute rather than an id or a class, because an id of ours in your stylesheet's way is a collision waiting to happen, and this reads as intent on your own button.
<button data-fellix-open>Talk to us</button>
The click is delegated from the document, so an opener your app renders later — after a
route change, inside a modal — works without re-registering anything. On an
<a> the default navigation is prevented, so you can write a real href as
a fallback for the JS-disabled case.
The #fellix-chat fragment
Arriving on a page whose URL ends in #fellix-chat opens the panel. This is how
the link in a reply notification brings someone back to the conversation. The fragment is
taken back off the address immediately, before anything can write it to history or pass it
on as a referrer, so the visitor is left on the page they came to read with the panel on
top of it.
JavaScript API
Available on window.fellixWidget as soon as the snippet has run. Because the
script is async, that is not necessarily before your own code runs — guard
with window.fellixWidget?. rather than assuming it is there.
fellixWidget.identify({ jwt, attributes })
window.fellixWidget?.identify({
jwt: '<token your backend signed>',
attributes: { plan: 'pro', seats: 12, trial: false },
})
Hands over the signed identity token, and optionally what your page claims about the
visitor. Call it after login in an app that learns who its user is late. Passing
jwt: null hands over claimed attributes without an identity.
Flat scalars only. Attribute values that are not a string, a number or a
boolean are dropped before anything is sent — a nested object or an array does not arrive
truncated, it does not arrive. Flatten it yourself if you need it:
{ 'plan.name': 'pro' }, not { plan: { name: 'pro' } }.
fellixWidget.update({ … })
Adds or replaces claimed attributes without touching the identity token. Merges — keys you
do not mention keep their previous value. Same flat-scalars rule as
identify.
fellixWidget.track(name)
window.fellixWidget?.track('checkout_started')
Names a moment on your page. It reaches the channel's targeting rules — so a proactive message can be written to fire on it — and lands in the organization's analytics. An empty or whitespace-only name is ignored.
fellixWidget.open()
Opens the panel, as though the visitor had pressed the launcher. Also dismisses the proactive greeting bubble if one is showing.
fellixWidget.close()
Closes the panel. The conversation is not ended and nothing is lost.
fellixWidget.logout()
Drops the identity, the claimed attributes and the visitor's thread. Call it on sign-out — otherwise the next person at that computer opens the previous one's conversation.
fellixWidget.destroy()
Removes the widget from the page entirely, thread included, and deletes
window.fellixWidget. For a page that stops being entitled to the widget
without navigating away — a single-page app where signing out only unmounts a component.
It performs a logout first and removes the
iframe a moment later rather than in the same tick. That delay is load-bearing: the logout
is a message to the panel, and the panel is the half that clears the stored thread. An
iframe removed immediately never receives it.
Signed identity
Anyone can open a chat anonymously; that is the normal case and needs nothing from you. Signed identity is for when the conversation should be attached to a person your system has already authenticated, and when the visitor's browser must not be able to claim to be someone else.
Your backend signs a token with the channel's signing secret — found under the channel's Security settings — and you hand it over in one of two ways:
-
server-rendered pages: put it straight on the tag as
data-jwt; -
single-page apps: call
fellixWidget.identify()once the login completes.
The secret is a secret. It signs identities, so it belongs on your server
and only there. Anything you put in front of a browser — including
data-key — is public.
Attributes passed alongside the token are claims your page makes, not verified facts. They are useful for targeting and for what an agent sees; do not use them for anything the visitor should not be able to influence.
What it touches on your page
The complete footprint, so you can predict it:
-
one
<button>appended to<body>, markeddata-fellix-launcher, at a very highz-index; -
one
<iframe>, created on first open, pointing at the panel's own origin; -
a click listener on
document— for[data-fellix-open]— plusresizeandmessagelisteners onwindow; -
three storage keys on your origin, all prefixed
fellix.webchat.:mutedandteasedinlocalStorage— the visitor's sound preference, and the fact that the proactive greeting has already been shown once — andopeninsessionStorage, so a panel left open survives the visitor clicking through your site but not their next visit. The conversation itself is not here: it lives in the panel's own storage, on Fellix's origin. Storage being denied outright costs a remembered preference and nothing more; - the page title, briefly, when an unread reply arrives while the tab is in the background.
Not touched: your stylesheet, your globals beyond
window.fellixWidget, your cookies, and the document's own scroll position.
destroy() reverses all of it.