Browser SDKCopy link to this section
The Floe browser SDK injects the AI overlay into your site or product. One script tag, one function call. No build step, no dependencies. The overlay renders on top of your existing UI inside an isolated container, handling voice AI conversation, Q&A, and guided actions without changing anything about your product.
InstallationCopy link to this section
Load the SDK, then initialize Floe from your own external script. Both are external and deferred, so Floe() runs after the SDK loads and nothing is inlined — this works under a strict Content-Security-Policy. Add this just before the closing </body> tag:
<!-- 1. Load the SDK (allow https://cdn.floe.so in your script-src) -->
<script src="https://cdn.floe.so/floe-sdk.iife.js" defer></script>
<!-- 2. Initialize from your own script -->
<script src="/floe-init.js" defer></script>
// floe-init.js — served from your own origin
const floe = Floe({
clientKey: "YOUR_CLIENT_KEY",
demoMode: true,
demoSiteId: "YOUR_SITE_ID",
});
floe.ready.then(() => console.log("Floe ready"));
// later: floe.disconnect();
Replace YOUR_CLIENT_KEY and YOUR_SITE_ID with the values from your site settings. Because both scripts are deferred, they execute in order after the page parses — floe-init.js runs once the SDK has loaded and exposed the global Floe() function, which mounts the overlay. Your product loads exactly as it did before. If your CSP allows inline scripts (or you use a per-request nonce), you can call Floe(...) from an inline <script> instead.
Floe() returns an SDK instance with a ready promise and a disconnect() method to tear the overlay down (shown above).
Integrate with a coding agentCopy link to this section
Paste the following prompt into your coding agent from the root of your web project. Tell it which Floe agent you want, or point it to the matching setup guide below.
Integrate the Floe browser SDK into this web app.
Requirements:
- Read https://floe.so/docs/sdk and the matching setup guide before editing:
Demo: https://floe.so/docs/demo-agent/setup
Website: https://floe.so/docs/website-agent/setup
Support: https://floe.so/docs/support
Onboarding: https://floe.so/docs/onboarding
- Load https://cdn.floe.so/floe-sdk.iife.js once, after the app is interactive.
- Initialize window.Floe(...) exactly once in the browser with the configuration
for the selected agent. Do not initialize during server rendering.
- Store the returned instance, handle instance.ready, and call
instance.disconnect() during teardown or logout.
- Guard against duplicate initialization, including React Strict Mode remounts.
- Read the client key from this app's public/browser environment configuration.
A Floe client key is domain-bound and safe for browser use. Never expose a
secret Floe API key or put access tokens in userInfo.metadata.
- Follow this repository's framework conventions and CSP policy. Use the CDN
script shown in Floe's docs; do not add an npm SDK dependency.
- Add a small verification note or test for script loading, single
initialization, and cleanup.
Before editing, inspect the app's framework, root layout, authentication lifecycle,
and existing third-party script pattern. Then implement the smallest idiomatic change
and summarize the files changed and how to verify it.
The agent-specific setup pages include ready-to-paste prompts and configurations. The rest of this page is the shared API reference.
Choosing a modeCopy link to this section
One embed runs one mode. Pick it with a single flag:
- Demo mode (
demoMode: true) — the live, voice-led demo agent. Drives your real product and streams it to the visitor. Pair withdemoSiteId. - Website agent (
websiteAgent: true) — the top-of-funnel Ask Floe Q&A launcher with contextual nudges, which can hand off in place to a live demo. Floe resolves the site fromclientKey; do not adddemoSiteIdfor this mode.
demoMode and websiteAgent are mutually exclusive. If both are set, demoMode wins. With neither set, the SDK mounts the in-product overlay used for onboarding and support.
Use the same two-script embed as above and set websiteAgent: true in your init script:
// floe-init.js — website agent (top-of-funnel Q&A that hands off to a demo)
Floe({ clientKey: "YOUR_CLIENT_KEY", websiteAgent: true });
Onboarding or support: who starts the conversationCopy link to this section
The in-product overlay (neither flag set) covers two jobs with different opening postures. Both render collapsed, and neither connects a voice or text session until the user clicks the launcher. activation picks the default nudge and opening:
activation: "proactive"(default) — the onboarding agent. Its setup-oriented nudge appears automatically for eligible users; clicking the launcher starts the session with onboarding-oriented suggestions. Use this right after signup.activation: "on_demand"— the support agent. The agent stays dormant: no prompt, no automatic nudge, and no session started until the user opens the launcher. When they do, it opens by asking what they need. Use this everywhere else in your product. The nudge is off by default here rather than unavailable — seenudgein the reference below to turn it back on.
// floe-init.js — support agent (in-product, user-initiated)
Floe({ clientKey: "YOUR_CLIENT_KEY", activation: "on_demand" });
on_demand changes the opening posture, not what the agent can do. Ask it to walk you through creating a report and it runs the same guided workflow the onboarding agent would.
Because the difference is per user rather than per surface, most products set it from their own signup state in one embed:
Floe({
clientKey: "YOUR_CLIENT_KEY",
activation: user.isNewlySignedUp ? "proactive" : "on_demand",
});
activation is ignored in demo and website-agent mode.
What the SDK doesCopy link to this section
Once mounted, the SDK:
- Renders the overlay into its own container on
document.body. For the website agent, Floe attaches a shadow root under that container, so your selectors don't match its internals and its styles don't leak out — though inherited properties and CSS custom variables still cross the boundary. The demo and in-product agents render in the normal document. - Establishes a voice connection after the visitor starts a session via WebRTC to the Floe voice server, so the visitor can talk to the agent and hear responses in real time. In the website agent the session starts with the visitor's microphone muted — they can type or unmute.
- Streams the demo in demo and website-agent mode: the agent drives your product in a server-side browser and the visitor watches it live, as video, inside the overlay.
- Captures screen context for the in-product agent so it understands what the user is looking at, during active sessions only. See Security.
The SDK does not intercept your network requests, and it does not touch your application's own content. It does make some changes outside its overlay, all of them either at startup or deliberate:
- At startup it adds a font
<link>and a stylesheet for its own cursor and highlight styles todocument.head. - In docked-sidebar mode it sets
margin-rightandmax-widthon<html>to make room for the sidebar, and marks the element withdata-floe-docked— see Website Agent Setup. - A full-screen demo temporarily locks background scrolling with inline styles on
<body>and<html>, restored when it closes. - The in-product agent clicks and fills elements on your page when it guides a user. That is the product working, not a side effect.
It does add a few things to document.head: a Google Fonts link, and a stylesheet for its own cursor and highlight styles. In docked-sidebar mode it also sets margin-right and max-width on <html> to make room for the sidebar, and marks the element with data-floe-docked — see Website Agent Setup for how to cooperate with that.
Configuration referenceCopy link to this section
Everything is passed to Floe() as a single config object.
| Option | Type | Description |
|---|---|---|
clientKey | string | Required. Your site's client key, from site settings. |
demoMode | boolean | Run the live demo agent. Default false. |
websiteAgent | boolean | Run the top-of-funnel Ask Floe Q&A launcher. Default false. Ignored if demoMode is also set. |
activation | "proactive" | "on_demand" | In-product overlay only. Both wait for a launcher click before connecting. proactive (default) auto-shows an onboarding nudge and uses a setup-oriented opening; on_demand defaults to no automatic nudge and uses a support-oriented opening. Ignored in demo and website-agent mode. An unrecognized value falls back to proactive. |
mcp | object | Support (activation: "on_demand") only. Connect one authenticated remote MCP server with { serverUrl, getBearerToken, allowedTools }. The token callback runs when the user opens support. Any compatible public HTTPS endpoint can be configured without a Floe deployment change, provided it uses the default HTTPS port and a DNS hostname, with no query string, fragment, or embedded credentials — those are rejected, which disables MCP for the session. Validation is all-or-nothing: an empty allowedTools, more than 20 entries, or a duplicate name disables the entire MCP connection for that session rather than skipping the bad entry. Support still works, without your tools. See Support: Live account data with MCP. |
consentMode | "not-required" | "pending" | Gate SDK identity and visitor analytics behind your own consent UI. not-required is the default. pending keeps the agent usable but defers browser identifiers, configured userInfo, identity capture, visitor/nudge events, and conversation continuity until consent("granted"). |
launcher | object | Closed-launcher placement for website, onboarding, and support agents: { position?: "bottom-right" | "bottom-left", inset?: number }. Defaults to bottom-right; inset is pixels from the viewport edge. |
exitIntent | true | object | Website agent or docked demo only. Opt in with true, or customize { enabled?, message?, minTimeOnPageMs?, thresholdPx? }. Defaults to a 5-second dwell and a 20px top-edge threshold. Values are clamped rather than rejected: message is truncated to 240 characters, minTimeOnPageMs to at most one hour, and thresholdPx to at most 100 — pass 300 and you get 100. Detection needs a real mouse, so it never fires on touch devices. |
demoSiteId | string | Demo mode only. The ID of the site to demo. The website agent resolves its site from clientKey. |
embedMode | "docked" | "fullscreen" | How the demo renders. docked (default) starts as a bottom-docked pill and goes full-bleed once the demo starts; fullscreen opens the demo immediately. Use fullscreen on dedicated demo pages. |
enableAudio | boolean | Whether the visitor's microphone starts enabled. Default true. It does not control whether the agent speaks — that's the speaker toggle in the UI, or toggleSpeakerMute(). Ignored by the website agent, which always starts with the mic muted. |
enableScreenCapture | boolean | In-product onboarding/support only. Share active-page context so the agent can guide the live UI. Default true; demo mode disables it. |
nudge | object | Onboarding/support launcher nudge: { text?: string, autoShow?: boolean, autoHideDelay?: number }. autoShow defaults to true for proactive and false for on_demand; set it explicitly to override. autoHideDelay is milliseconds and 0 keeps it visible. Website contextual nudges are configured in the dashboard, not here. |
userInfo | object | Current-session context: { externalId?, email?, name?, company?, designation?, metadata? }. The browser supplies this object unsigned, so Floe may use it to personalize or pre-fill the live session but does not treat it as verified identity or use it to unlock another browser's history. Use metadata only for non-secret context. |
prospectEmail | string | Pre-fill the prospect's work email so the intro form skips the email step. Collect it on your own page first to gate the session behind real intent. Pre-filling shrinks what the form asks for; it never starts the session on its own — the prospect's submit does, so a page load alone is never billed. |
websiteAgentDisplayMode | "floating" | "sidebar" | Website agent only. Override the dashboard display setting for testing or staged rollout. Without an override, the dashboard setting applies. |
websiteAgentSidebarWidth | number | Website agent sidebar width in pixels. Clamped to 320–480; default 400. |
demoCalendarLink | string | Optional HTTPS booking URL override. Falls back to the site's calendar URL. Floe does not append prospect identity or contact-claim data to this URL. An HTTP URL or a URL with embedded credentials is ignored. |
demoLinkId | string | Demo mode only. The ID of the demo link this session came from. This is how Floe knows which link was used, so anything you configured per-link — persona, discovery, identity capture — applies. Omit it and the session silently falls back to the site defaults. The hosted demo-link page sets this for you; you only need it when you host your own demo page. |
demoIdentityCapture | object | Demo mode only. Override when the intro form asks for identity, instead of using the site's setting. |
skipOnboardingModal | boolean | In-product only. Treat the user as returning rather than new, which changes the launcher's greeting. It does not change whether a session starts. |
apiUrl | string | Override the Floe API URL. Defaults to production. |
debug | boolean | Verbose console logging. Default false. |
Identity and browser continuityCopy link to this section
userInfo is useful context, including inside an authenticated application,
but it still arrives from browser JavaScript. An externalId or email in that
object does not authenticate the person, merge visitor records, or make prior
activity from another browser available. Floe uses those values only in the
current session as identity context. If a visitor deliberately submits a
pre-filled email through the Website Agent or Demo Agent capture flow, that
submission is stored under the contact-claim rules below.
When visitor collection is enabled and consent permits it, Floe instead uses a
server-issued, opaque visitor token for same-browser continuity. The token is
site-specific and stored first-party. It can resume that browser's active
conversation and select retained conversation or demo context owned by that
exact browser record; it cannot prove which human is using the browser. During
SDK startup, the status check runs only when this token already exists. It does
not send userInfo, email, or externalId, and it does not mint a token merely
to perform the check. For the in-product overlay, a retained conversation or
direct demo on that exact browser record marks the browser as returning, so the
first-run onboarding modal is not shown again. Clearing site storage or
switching browsers starts a separate continuity record.
An email collected by the Website Agent or Demo Agent is stored as a contact claim attached to the exact browser/session that supplied it. It is not turned into a canonical person record. Two different browser records may therefore share the same address without being merged, and the address alone never reveals either browser's prior conversation.
Recap Schedule linksCopy link to this section
When a claim-backed prospect recap sends its Schedule a follow-up link back
to a customer page, the opaque action capability is carried in the URL fragment.
The browser does not send that fragment in its initial HTTP request. At the very
start of Floe() initialization, the SDK removes it from the address bar before
client-key validation, analytics bootstrap, or SDK-rendered assets can observe
the URL. It then submits the capability in a POST body and, when the action is
accepted, navigates only to the validated HTTPS booking destination.
The SDK supplies only an existing, consented visitor token. It never creates or adopts a browser identity to redeem the action. An exact match with the browser source that supplied the address can confirm that one claim. A different browser, a cleared browser identity, or a pending/denied consent state can still continue to scheduling, but cannot confirm the address, join histories, or unlock cross-device context.
If Floe uses its hosted fallback page instead, that page removes the fragment before any request and waits for an explicit Continue to scheduling button. See Prospect recap actions for the recipient-facing flow and the separate Not you? action.
Launcher placementCopy link to this section
The closed launcher defaults to the bottom-right. Use bottom-left when that
corner is occupied by another widget; inset optionally sets its pixel distance
from the viewport edge. This applies to website, onboarding, and support
agents. Demo mode has no closed launcher.
Floe({
clientKey: "YOUR_CLIENT_KEY",
websiteAgent: true,
launcher: { position: "bottom-left", inset: 24 },
});
Exit intentCopy link to this section
Exit intent gives a visitor one final invitation without pretending they chose to start a conversation. It is opt-in and works with the Website Agent or a docked Demo Agent:
const floe = Floe({
clientKey: "YOUR_CLIENT_KEY",
websiteAgent: true,
exitIntent: {
message: "Before you go — want to see it live?",
minTimeOnPageMs: 5000,
thresholdPx: 20,
},
});
exitIntent: true uses the same defaults shown above. An object enables the
feature unless enabled: false. Once the dwell time has passed, Floe watches
for a desktop pointer leaving through the browser's top edge and opens the
dormant surface at most once for that SDK instance/page. Ordinary movement
inside the page does not trigger it.
- The Website Agent opens its dormant panel with an exit-specific greeting.
- A docked Demo Agent expands its existing intro pill.
- Fullscreen demos and the onboarding/support overlay do not support exit intent.
Opening the surface is free and passive. It does not start a paid session, request microphone access, or send a visitor turn. The visitor must click or type before any of those actions can happen.
Runtime and custom signalsCopy link to this section
Use the instance methods when consent, targeting, or the abandonment signal is owned by your application:
enableExitIntent({ message?, minTimeOnPageMs?, thresholdPx? })disableExitIntent()showExitIntent({ message? })
await floe.ready;
// Enable or replace automatic desktop detection at runtime.
floe.enableExitIntent({ minTimeOnPageMs: 8000, thresholdPx: 16 });
// A customer-owned mobile or funnel signal can show the same passive surface.
checkoutFlow.onAbandonment(() => {
const shown = floe.showExitIntent({ message: "Need help before you go?" });
if (!shown) showExistingFallback();
});
// Stops automatic detection; manual showExitIntent() remains available.
floe.disableExitIntent();
enableExitIntent(options?) returns false only when the selected agent mode
cannot provide an exit-intent surface. Calling it before ready is safe.
showExitIntent(options?) returns true only when an eligible mounted surface
claims the request; it returns false before readiness, after the visitor has
engaged, or once the one-shot invitation has already appeared. Automatic and
manual triggers share that same one-shot allowance. When a manual call omits
message, it reuses the message in the current exit-intent configuration.
EventsCopy link to this section
Floe() returns an instance that emits events. Subscribe with on(event, handler).
const floe = Floe({ clientKey: "YOUR_CLIENT_KEY", websiteAgent: true });
floe.on("ready", () => console.log("overlay mounted"));
floe.on("connected", () => console.log("session started"));
floe.on("demoEnded", ({ summary, featuresShown, nextStep }) => {
console.log(summary, featuresShown, nextStep);
});
Available in every mode
| Event | Payload | Fires when |
|---|---|---|
ready | — | The overlay has mounted. Note that this does not fire if your client key is rejected or the agent is disabled for the site, even though Floe() itself returns normally. Use this event, not the instance's ready promise, as proof the overlay is live. |
connected | — | A session has started. This is the moment billing begins. |
disconnected | — | The session ended. May fire more than once for a single teardown. |
closed | — | The SDK was torn down via disconnect(). Distinct from disconnected, which is about the call. |
botReady | — | The agent is initialized and ready to talk. |
userSpeaking | boolean | The visitor started or stopped speaking. |
botSpeaking | boolean | The agent started or stopped speaking. |
userTranscript | { text, final } | A speech-to-text result for the visitor. Fires for interim results too, so expect several per spoken sentence. There is no matching event for the agent's words. |
languageChanged | { language, source, changed } | The session switched language. See Languages & Voices. |
error | varies | Something failed. The payload is either a thrown Error or an object with a type discriminator — feature-detect payload?.type before reading it. |
Demo and website agent
| Event | Payload | Fires when |
|---|---|---|
demoEnded | { summary, featuresShown, nextStep } | The demo finished and the agent produced its wrap-up. |
exitIntentShown | { agentMode, customMessage } | The exit-intent surface was revealed. |
exitIntentEngaged | { agentMode } | A session started after exit intent had been shown. |
calendarRequested | { requestId } | The agent offered the booking calendar. Website agent only, and only when a booking URL is configured. |
In-product agent (onboarding and support)
| Event | Payload | Fires when |
|---|---|---|
expanded / minimized | — | The user opened or collapsed the overlay. |
sessionPaused | server payload | The session was paused for inactivity and can be resumed. |
sessionStopped | — | The session was stopped via stopSession(). |
planStarted | { planName, totalSteps } | A guided workflow began. |
planProgress | { planName, currentStep, totalSteps, progressPercent, currentTaskTitle } | The user advanced a step. |
planComplete | { planName } | The workflow finished. |
elementHighlighted | { target, description } | The agent highlighted an element on your page. |
Instance methodsCopy link to this section
| Method | Returns | What it does |
|---|---|---|
requestDemo(options?) | Promise<boolean> | Opens the agent already on the page, from your own button. Resolves false if no Floe surface handled it. See Opening the demo from your own button. |
disconnect() | Promise<void> | Ends any live session and unmounts the overlay. |
getStatus() | { initialized, connected, sessionId, microphoneMuted } | Current state. microphoneMuted reads false before a session exists. |
getSessionId() | string | The SDK's per-start correlation ID sent when a session starts—not the server's recording or demo-session record ID. Each start or restart gets a fresh ID; disconnect() leaves the last ID readable until another session starts. |
sendTextMessage(text) | Promise<void> | Sends a message as if the user typed it. Queued if the agent isn't ready yet. |
toggleMute() | boolean | Toggles the visitor's microphone. Returns the resulting state. |
toggleSpeakerMute() | boolean | Toggles whether the agent speaks aloud, and tells the server to stop generating speech — except while a live demo is on screen, where speech generation continues because the demo's captions are derived from it. |
enableExitIntent(options?) | boolean | Arms exit-intent detection at runtime. |
disableExitIntent() | void | Disarms the detector. showExitIntent() still works afterwards. |
showExitIntent(options?) | boolean | Shows the exit-intent surface immediately, from your own signal. Never connects or touches the microphone. |
consent(decision) | void | With consentMode: "pending", "granted" begins identity and analytics collection. "denied" stops collection in either consent mode and is sticky for the current page load. |
Consent gatingCopy link to this section
Use consentMode: "pending" when your application already owns the consent
banner and Floe must wait for its decision:
const floe = Floe({
clientKey: "YOUR_CLIENT_KEY",
websiteAgent: true,
consentMode: "pending",
});
cookieBanner.onAccept(() => floe.consent("granted"));
cookieBanner.onReject(() => floe.consent("denied"));
consent("granted") changes behavior only while consentMode is "pending".
consent("denied") is honored in both "pending" and "not-required" modes.
Before grant, the Floe surface still renders and a visitor can keep chatting,
but Floe does not associate ordinary activity with a browser identity, attach
configured userInfo, collect visitor or nudge analytics, retain a Website
Agent conversation, or show Website Agent identity capture. A direct Demo Agent
form is a deliberate exception: values the visitor deliberately submits through
that form, including an email they type or accept as prefilled, are sent to
start or personalize the demo they requested even while consent is pending or
denied. Outside that submitted form, Floe still withholds configured userInfo,
browser identifiers, visitor analytics, and conversation continuity until
grant.
Grant begins identity and visitor-analytics collection from that point forward. Website Agent recording and retention are fixed when a conversation starts: one already running before the grant stays unretained, while the next conversation can be retained. Earlier activity is never added later.
Denial stops queued and future identity and visitor-analytics collection for
that SDK instance and removes a visible Website Agent identity-capture card.
Website Agent recording posture is fixed when a conversation starts: if
recording was permitted then, end that conversation to stop it; any later
Website Agent conversation in the denied page load starts unretained. Denial is
sticky for the current page load, so a later consent("granted") call is
ignored. Denial is not a deletion request and does not erase data already sent
before the decision; use your normal data deletion process for that.
Only one Floe instance can be active per page. A second Floe() call still returns an instance, but its ready promise rejects — so attach a .catch() rather than a try/catch, and call disconnect() on the first instance before starting another. This is the usual React Strict Mode failure.
React usageCopy link to this section
Initialize Floe from the script's onLoad signal, and disconnect on unmount:
import Script from "next/script";
import { useEffect, useRef } from "react";
export function FloeDemo() {
const floeRef = useRef<{ disconnect?: () => void } | null>(null);
// Tear the overlay down when the component unmounts.
useEffect(() => () => floeRef.current?.disconnect?.(), []);
return (
<Script
src="https://cdn.floe.so/floe-sdk.iife.js"
strategy="afterInteractive"
onLoad={() => {
if (floeRef.current) return; // guard against double-init
floeRef.current = (window as any).Floe({
clientKey: "YOUR_CLIENT_KEY",
demoMode: true,
demoSiteId: "YOUR_SITE_ID",
embedMode: "docked",
});
}}
/>
);
}
onLoad fires once window.Floe is available, so it's the safe place to initialize. The ref guards against a double-init and disconnects the overlay when the component unmounts.
Opening the demo from your own buttonCopy link to this section
Your page probably has its own "See it live" call-to-action — in the hero, the nav, the footer. Those sit outside the agent, so they need a way to drive it.
Call requestDemo() on the instance Floe() returned. It surfaces the agent already on the page rather than launching a second one, and it works the same whichever agent you embedded:
- Website agent — opens the panel with the demo request already sent, so the visitor lands mid-conversation instead of on an empty chat.
- Demo agent — expands and focuses the docked pill.
// floe-init.js
const floe = Floe({
clientKey: "YOUR_CLIENT_KEY",
websiteAgent: true,
});
document.querySelector("#see-it-live").addEventListener("click", async () => {
if (!(await floe.requestDemo())) {
// Nothing took the request — fall back to whatever you'd do without Floe.
window.open("/book-a-call", "_blank");
}
});
Check the result. requestDemo() resolves true only if an agent actually took the request. It resolves false when the SDK never booted, when your site has no demo available yet, when the agent you embedded isn't enabled for your site, or when the agent is still an older cached build — all cases where a button that appears to work would do nothing at all. Give the visitor somewhere else to go.
It waits briefly before answering (1500ms by default, override with { timeoutMs }), because a click that lands before the agent has finished mounting is indistinguishable from one with nothing listening. Keep that budget under about five seconds if your fallback opens a new tab — browsers stop treating the click as user-initiated after that and will block the popup.
For the in-product onboarding/support agent there is no demo to open, so requestDemo() returns false immediately.
Verifying the installationCopy link to this section
- Open your product in Chrome.
- Open DevTools (F12) → Console.
- Type
window.Floeand press Enter. You should see a function. - Confirm the overlay (demo pill or Ask Floe launcher) appears on the page.
If window.Floe is undefined, check that the script tag is present in the page source and loaded without error. If it's defined but nothing appears, check that clientKey is correct and matches your site settings.
How it loadsCopy link to this section
The SDK is served from a global CDN as a single IIFE bundle that attaches window.Floe. The overlay renders into a container appended to the document body. For the website agent, Floe attaches a shadow root beneath that container, which scopes selectors on both sides — inherited properties and CSS custom variables still cross it. The demo and in-product agents render in the light DOM inside a fixed, high-z-index container.
Framework compatibilityCopy link to this section
The SDK works with any web framework or none — React, Vue, Angular, Svelte, plain HTML. Because it's a script tag plus a function call, and mounts its own container on document.body, it sits outside your application's component tree entirely.
FAQCopy link to this section
Does the SDK work on single-page applications? Yes. It detects route changes automatically and re-scopes the agent's context — including the website agent's page-scoped nudges — as the user navigates within your SPA.
Will the SDK slow down my product? No. It loads asynchronously and does not block rendering. On pages where the overlay is never opened, the impact is a single small network request.
Can I run demo and website-agent mode from one embed?
No — one embed runs one mode. demoMode and websiteAgent are mutually exclusive (demoMode wins if both are set). Use the mode that fits the surface.
Can I use different keys in development and production? Yes. Use your development client key in dev and the production key in prod. Demo mode also needs the matching site ID. The dashboard shows each site's sessions separately.
What if I need to remove the SDK later?
Call disconnect() on the instance returned by Floe() — that unmounts the overlay and ends any live session. Removing the script tag alone does not tear down an overlay that has already mounted. disconnect() removes the overlay and its stylesheets, but it does not clear browser storage. What survives it: the font <link> tags added to document.head; the first-party anonymous ID the website agent keeps in local storage; and, for the website agent, three sessionStorage keys holding the open/closed state and the last few turns of the chat so it can be restored on the next page load in the same tab. Local storage persists until the visitor clears site data; the sessionStorage entries are dropped when the tab closes.