Brandfine Docs
SDK

Live Chat

Put the Brandfine Live Chat bubble on your site — fetch the bootstrap server-side, inject the widget client-side, zero secrets in the browser.

bf.liveChat renders the chat widget for this workspace. It's a server-half / client-half pair, same shape as analytics: your server fetches the bootstrap with the secret workspace key, your client injects the widget with only the publishable key — the browser never sees a secret.

// Server half (RSC / build step) — secret key stays here
import { createBrandfineClient } from '@brandfine/client'

const bf = createBrandfineClient({
  baseUrl: process.env.BRANDFINE_API_URL!,
  apiKey: process.env.BRANDFINE_API_KEY!,   // broad key — server-only
})
const config = await bf.liveChat.getConfig()
// → pass `config` to your client component as a prop
// Client half — no secrets, pure DOM injection
'use client'
import { useEffect } from 'react'
import { createBrandfineClient, type LiveChatBootstrap } from '@brandfine/client'

export function LiveChatClient({ config }: { config: LiveChatBootstrap | null }) {
  useEffect(() => {
    if (!config) return
    const bf = createBrandfineClient({
      baseUrl: process.env.NEXT_PUBLIC_BRANDFINE_API_URL!,
      apiKey: 'unused', // install() never sends it
    })
    void bf.liveChat.install({ config })
  }, [config])
  return null
}

Drop the server component once in your root layout. After that, everything is controlled from the CMS — activating, deactivating, greeting, theme all take effect on the visitor's next page load, with no redeploy of your site. The widget's first runtime call re-checks the enabled state, so even static-export sites (where the server half runs at build time) follow the CMS toggle live.

API

getConfig()Promise<LiveChatBootstrap>

Server-side only — calls GET /external/live-chat/bootstrap with the broad workspace key. Resolves to:

{ enabled: false }
// or
{
  enabled: true,
  publishableKey: string,    // brandfine_pk_… — safe in public HTML
  greeting: string | null,
  offlineMessage: string | null,
  theme: Record<string, string> | null,   // --bf-chat-* CSS vars
  scriptPath: string,        // widget bundle path, relative to baseUrl
}

{ enabled: false } means the workspace hasn't activated Live Chat (or toggled it off) — pass it through; install() no-ops.

install(opts)Promise<LiveChatInstallResult>

Client-side half. Appends the widget host (<div data-bf-live-chat data-publishable-key="…">) and the widget <script> to the page. Idempotent via a marker attribute — safe under StrictMode double-invokes and SPA re-mounts. Resolves to:

{ installed: true }
{ installed: false, reason: 'disabled' }           // config.enabled === false
{ installed: false, reason: 'ssr' }                // no document — call client-side
{ installed: false, reason: 'already-installed' }  // prior call won

Unlike analytics.install(), the config option is required — fetching here would need the broad key in the browser, which is exactly what this design avoids.

Plain HTML alternative

No SDK? Paste the snippet from Plugins → Live Chat → Manage settings → Integrate in the CMS:

<div data-bf-live-chat data-publishable-key="brandfine_pk_…"></div>
<script src="https://api.brandfine.co/widgets/live-chat.js" defer></script>

Same widget, same behavior. The publishable key can only start chat conversations for its workspace — it is designed to be public.

Theming

The widget renders in a Shadow DOM; your site's CSS can't break it and vice versa. Brand it via --bf-chat-* custom properties — set them in the CMS (they arrive through theme) or inline on the host div:

<div
  data-bf-live-chat
  data-publishable-key="brandfine_pk_…"
  style="--bf-chat-primary:#0b1f2a; --bf-chat-radius:10px;"
></div>

Available: --bf-chat-primary, --bf-chat-primary-contrast, --bf-chat-surface, --bf-chat-text, --bf-chat-muted, --bf-chat-border, --bf-chat-visitor-bubble, --bf-chat-radius, --bf-chat-font, --bf-chat-offset, --bf-chat-z.

On this page