Skip to main content
FirstFlow is the onboarding and activation layer for your AI agent and chat products. You author experiences AI-composed guides, tours, surveys, and announcements in a visual builder, and deliver them inside your chat UI with a drop-in widget. What shows, to whom, and when is decided server-side, so changing an experience never requires a code deploy. The integration contract is small: install @firstflow/sdk on your backend, mount @firstflow/react in your frontend, wrap your existing LLM client, and add a provider plus a widget. From there FirstFlow watches each conversation turn, decides whether anyone qualifies for an experience, composes the widget, and pushes the finished result down a realtime socket.
FirstFlow is open source (MIT) and runs entirely on your own infrastructure your app, your Supabase project, and your experiences in git. There is no FirstFlow service in the path. Start with Self-hosting, or read the source on GitHub.

What you can build

Everything you ship is an experience a unit of content with a type that determines how it renders. There are four: A guide is a single AI-composed widget that responds to the conversation in context. You author a flow graph; at runtime the backend walks it to a Result node and Anthropic (claude-sonnet-4-6) renders the answer, walkthrough, or CTA as rich blocks. A tour is a multi-step flow that walks users through features, rendered directly in chat. A survey collects NPS, scales, multiple choice, and open questions, answered natively in the chat surface. An announcement is a feature banner or update card rendered inline or as an overlay. See Experiences for the full model, and Widgets & blocks for how a WidgetTree renders to DOM. Two more capabilities cut across all experience types. Slash commands put a / palette in your composer that triggers an experience or a host-app action. Audience targeting segments end-users by traits so each experience reaches only the people you intend.

How it works

The mental model is one rule: eligibility is evaluated server-side, and finished widgets are pushed to the browser. The browser SDK is a passive consumer it renders what arrives and reports activity back. That is why you cannot trigger a widget purely from the client: you wrap your LLM call (or call notifyActivity()), and the server decides whether anyone qualifies. Concretely, your wrapped LLM client tags each completion and the SDK fires a fire-and-forget POST /v1/conversations/:conversationId/messages to the backend, which returns 202 and processes asynchronously. The backend’s ConversationRouterService derives a trigger type from the message role (after_user_message, after_user_and_agent_message, or conversation_classifier), finds experiences whose triggers and audience match, resolves any classifier or branch-decision gate, walks the flow graph to a Result node, and asks Anthropic to compose a WidgetTree. That tree is emitted over Socket.IO to the room member:{workspaceId}:{userId}, where @firstflow/react renders it. When the user interacts, a widget.action travels back and flow navigation (flow.next / flow.back) is resolved server-side, composing and pushing the next step. For the full architecture authentication, transport, and the realtime room model see How it works.

How it fits your app

FirstFlow plugs in from two sides, and they are linked by one id you own. On the browser, @firstflow/react opens the realtime connection, identifies the end-user, and renders whatever FirstFlow decides to show. Mount FirstflowProvider once above your chat, drop in FirstflowWidget, and pass your already-authenticated user FirstFlow trusts that value, so your own auth provider verifies who the user is.
The publishableKey (pk_live_…) is browser-safe and not a secret. conversationId is optional and lazy: omit it until the first message, then mint a stable id and reuse it across reloads. See Conversations & sessions. On the server, @firstflow/sdk wraps your existing LLM client so every turn, trace, and cost flows to FirstFlow with no extra calls. Tag each observed call with all three of firstflowAgentId, sessionId, and userId they are stripped from the request body before it reaches OpenAI or Anthropic.
All three tags are required. If you provide only some of firstflowAgentId, sessionId, and userId, the call still runs but passes through unobserved FirstFlow logs a one-time warning and never sees that turn. Identity is never inferred.
The single shared id conversationId in the browser, sessionId on the server is what lines up the widget, the stored transcript, the branch-decision classifier, traces, and any flow run onto one conversation. The server SDK reads FIRSTFLOW_API_KEY (a secret, server-only) from the environment; never hard-code it and never expose it to the browser. Anthropic and OpenAI-compatible endpoints (Ollama, vLLM, LM Studio) work the same way through @firstflow/sdk/anthropic and @firstflow/sdk/aiclient; LangChain apps attach the FirstflowCallbackHandler instead.

Choose your entry point

Most teams start with the Quickstart: wrap one LLM call, mount the provider, and watch a live widget appear. From there, the two SDKs are the natural next stops pick the side you are integrating first.

React SDK

Mount the provider, render the widget, identify users, and run slash commands in the browser.

Server SDK

Wrap your LLM client to observe conversations, traces, costs, and outcomes.
If you want the concepts first, read Core concepts; to run the whole platform on your own infrastructure, start with Self-hosting; and to drive FirstFlow from a coding agent like Claude Code or Cursor, see the MCP overview.