Pipecat UI
Hooks

usePipecatApp

Client bootstrap hook — builds a PipecatClient for a chosen transport and owns the connect/disconnect lifecycle.

Why

Standing up a Pipecat client by hand means choosing and importing a transport, constructing the client, sequencing device init, and writing the connect flow — including the server-side bot-start handshake — with correct error handling and teardown. usePipecatApp is that boilerplate as one hook. It bootstraps:

  • Transport creation — calls your transportFactory (sync or async), or an app-owned registered loader. Only the package you import is bundled.
  • Client constructionnew PipecatClient(...) with sensible defaults (mic enabled, camera off), your clientOptions merged over them, and an onClient callback fired before anything else so early event subscribers miss nothing.
  • The connect flow — one connect() that picks the right strategy: bot-start via startBot() (with response transformation and smallwebrtc ICE-server wiring), the equivalent start-then-connect flow for endpoint params, or a plain connect().
  • Lifecycle guards — connection attempts are ignored outside connectable states (no double connects), teardown disconnects exactly one client even under React StrictMode's double-mounted effects.
  • Real error state — transport-load, device-init, and connect failures all surface in error with their actual messages instead of dying in the console.
  • Stability — nothing you pass needs memoizing; options are read when used, so inline object literals don't churn the client.

Installation

pnpm dlx shadcn@latest add @pipecat/use-pipecat-app

Transport packages are not installed with this hook — they load on demand so your app only ships the one it uses. Install the package for your transportType:

TransportInstall
smallwebrtc (default)npm install @pipecat-ai/small-webrtc-transport
dailynpm install @pipecat-ai/daily-transport
websocketnpm install @pipecat-ai/websocket-transport
moqnpm install @pipecat-ai/moq-transport

Import the installed transport in your app and pass transportFactory. The registry helper does not reference optional packages, so Vite and Next.js do not need unused transports installed. A missing package that your app imports is correctly reported by its bundler.

For a lazy import:

transportFactory: async () => {
  const { SmallWebRTCTransport } =
    await import("@pipecat-ai/small-webrtc-transport");
  return new SmallWebRTCTransport();
};

For an app that selects between transport types, register only the loaders it supports before mounting the hook:

import { registerTransport } from "@/lib/transports";

registerTransport("smallwebrtc", async () => {
  const { SmallWebRTCTransport } =
    await import("@pipecat-ai/small-webrtc-transport");
  return SmallWebRTCTransport;
});

Calling the hook without a factory or registered loader surfaces a setup error.

Usage

The hook renders nothing — pass the client it returns to a PipecatClientProvider yourself, and every kit component works beneath it:

"use client";

import { PipecatClientProvider } from "@pipecat-ai/client-react";
import { SmallWebRTCTransport } from "@pipecat-ai/small-webrtc-transport";

import { ConnectButton } from "@/components/pipecat/connect-button";
import { usePipecatApp } from "@/hooks/use-pipecat-app";
import { Spinner } from "@/components/ui/spinner";

export function App() {
  const { client, connect, disconnect, error } = usePipecatApp({
    transportFactory: () => new SmallWebRTCTransport(),
    connectParams: { endpoint: "/api/start" },
  });

  if (!client) return error ? <p role="alert">{error}</p> : <Spinner />;

  return (
    <PipecatClientProvider client={client}>
      <ConnectButton onConnect={connect} onDisconnect={disconnect} />
      {error && <p role="alert">{error}</p>}
    </PipecatClientProvider>
  );
}

connect() picks its flow from the options: with startBotParams it calls client.startBot() first (optionally transforming the response, and wiring iceConfig.iceServers onto a smallwebrtc transport), an endpoint-shaped connectParams starts a bot and connects after checking the client is still active, and anything else goes straight to client.connect().

The client is created once per transportType — every other option is read when it's used, so nothing you pass needs memoizing and changing connectParams between attempts just works. The trade-off: transportOptions , transportFactory, and clientOptions are read once at creation. To apply a change there, remount the calling component with a React key.

Key options

OptionTypeDescription
transportType"smallwebrtc" | "daily" | "websocket" | "moq"Transport backing the client (default "smallwebrtc"). The only option that rebuilds the client.
transportFactoryTransportFactoryCreates the transport; may return a promise. Read once at client creation.
transportOptionsTransportOptionsTransport constructor options, read once at creation.
clientOptionsPartial<PipecatClientOptions>Merged into the client constructor (defaults: mic on, cam off).
connectParamsTransportConnectionParams | APIRequestConnection params, or an endpoint object for start-and-connect.
startBotParamsAPIRequestWhen set, connect() starts the bot first and connects with the response.
startBotResponseTransformer(r) => r | Promise<r>Transforms the startBot response before connecting.
connectOnMountbooleanConnect as soon as the client is ready (default false).
initDevicesOnMountbooleanRun client.initDevices() once the client exists (default false).
onClient(client) => voidFired once per created client, before device init or connect.

Returns

FieldTypeDescription
clientPipecatClient | nullNull until the transport loads and the client is built.
connect() => Promise<void>Starts the session; no-op unless state is initialized, disconnected, or error.
disconnect() => Promise<void>Ends the session.
errorstring | nullReal failure message from transport load, device init, or connect.
clearError() => voidManually dismiss error (retrying connect() also clears it).
rawStartBotResponseunknownLatest client.startBot() response.
transformedStartBotResponseunknownThat response after the transformer ran.

On this page