Home / Docs / Browser SDK

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 with demoSiteId.
  • 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 from clientKey; do not add demoSiteId for 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 — see nudge in 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 to document.head.
  • In docked-sidebar mode it 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.
  • 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.

OptionTypeDescription
clientKeystringRequired. Your site's client key, from site settings.
demoModebooleanRun the live demo agent. Default false.
websiteAgentbooleanRun 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.
mcpobjectSupport (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").
launcherobjectClosed-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.
exitIntenttrue | objectWebsite 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.
demoSiteIdstringDemo 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.
enableAudiobooleanWhether 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.
enableScreenCapturebooleanIn-product onboarding/support only. Share active-page context so the agent can guide the live UI. Default true; demo mode disables it.
nudgeobjectOnboarding/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.
userInfoobjectCurrent-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.
prospectEmailstringPre-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.
websiteAgentSidebarWidthnumberWebsite agent sidebar width in pixels. Clamped to 320–480; default 400.
demoCalendarLinkstringOptional 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.
demoLinkIdstringDemo 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.
demoIdentityCaptureobjectDemo mode only. Override when the intro form asks for identity, instead of using the site's setting.
skipOnboardingModalbooleanIn-product only. Treat the user as returning rather than new, which changes the launcher's greeting. It does not change whether a session starts.
apiUrlstringOverride the Floe API URL. Defaults to production.
debugbooleanVerbose 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.

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

EventPayloadFires 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.
userSpeakingbooleanThe visitor started or stopped speaking.
botSpeakingbooleanThe 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.
errorvariesSomething 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

EventPayloadFires 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)

EventPayloadFires when
expanded / minimized—The user opened or collapsed the overlay.
sessionPausedserver payloadThe 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

MethodReturnsWhat 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()stringThe 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()booleanToggles the visitor's microphone. Returns the resulting state.
toggleSpeakerMute()booleanToggles 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?)booleanArms exit-intent detection at runtime.
disableExitIntent()voidDisarms the detector. showExitIntent() still works afterwards.
showExitIntent(options?)booleanShows the exit-intent surface immediately, from your own signal. Never connects or touches the microphone.
consent(decision)voidWith consentMode: "pending", "granted" begins identity and analytics collection. "denied" stops collection in either consent mode and is sticky for the current page load.

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

  1. Open your product in Chrome.
  2. Open DevTools (F12) → Console.
  3. Type window.Floe and press Enter. You should see a function.
  4. 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.