docs

Installation

For Angular, use the Angular guide. It has its own AI install prompt.

This is the complete version of Quick Start. The browser uses the same tool panels, stores, filters, actions, snapshot providers and sync protocol as React Native. React Native Web renders the panels, and small browser modules handle storage, routing, DOM inspection, the clipboard, images and performance timing. Settings, account status and modal persistence are shared too.

For 3D apps, use the three.js (Beta) setup. That beta needs the matching beta packages.

Requirements

ReactReact and React DOM
Rendererreact-native-web 0.21
Buoy packages7.0.41 or later, imported from their /web entries
Not neededReact Native, Expo

Keep the libraries your tools inspect, such as Zustand, Jotai, React Redux or TanStack Query.

Install with your agent

Paste the prompt into Claude Code, Cursor or Codex. Your agent reads your app and picks the tools. Review its changes before you commit them.

Let your coding agent install it
Read the prompt before you paste it
Install Buoy, the in-app devtools, in this React web project. Read the full instructions first: curl -fsSL https://buoy.gg/install-web.md Read the raw text, not a summary. If curl is unavailable, use any HTTP tool you have. Ask me to paste the document only if nothing can fetch it. Do the install yourself: inspect the repo, run the commands, edit the files. Do not hand me steps you can run. Work out the routine decisions from the repo: package manager, which app to target, the framework, where the early hook goes, where the host mounts, which Buoy tools match the dependencies already installed. If Buoy is already partly installed, repair and extend it; never add a second host, provider, or package set. When instructions conflict, follow this order: what I say here, then the document's rules about which packages exist and what needs my permission, then this project's own constraints, then the rest of the document, then your judgment. Ask me before: adding a dependency other than Buoy packages and react-native-web, showing Buoy outside development, opening a browser or creating an account, changing what a release build does beyond what the document specifies, or anything hard to undo. Do not commit. You are not done when the package installs. Run this project's existing typecheck and build; add no tooling. Then report what you installed and why, what you skipped and why, every file you changed, what you verified, and the exact steps I take in the browser to confirm the menu appears and captures a request.
The prompt asks your agent to inspect the project, choose matching tools and guide setup. Review its edits and verify the connection on your device.Read the setup instructions

Packages

npm install @buoy-gg/core react-native-web

For plain Markdown readers, the command is:

bash
npm install @buoy-gg/core react-native-web
npm install @buoy-gg/core react-native-web

Then install the tools you want, for example @buoy-gg/network or @buoy-gg/zustand. Import the /web entries explicitly. That selects the browser API in TypeScript and in server bundlers. Browser bundlers also pick these builds through the packages' browser export condition. Native apps keep using the root imports.

The Vite plugin

In a Vite app, add buoy() to your plugins:

ts
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { buoy } from '@buoy-gg/core/vite';

export default defineConfig({
  plugins: [react(), buoy()],
});
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { buoy } from '@buoy-gg/core/vite';

export default defineConfig({
  plugins: [react(), buoy()],
});

It finds the Buoy tools your app installed. A tool counts when your package.json lists it. Buoy loads those tools, so you write no imports. The dev server restarts when your tool list changes. Leave a tool out with buoy({ exclude: ['console'] }). The plugin also adds the early hook below to index.html. Turn that off with buoy({ register: false }). The hook loads in release builds too. It stays off there unless Buoy ran in that browser in the last week. The plugin needs Buoy 7.0.62 or later.

The early DevTools hook

The Vite plugin adds this hook for you. Without the plugin, put this import at the top of your entry file, before React DOM. Render, layout, focus and element inspection need it, and so does Network if you want the requests made while the page loads:

ts
import '@buoy-gg/core/web/register';
import '@buoy-gg/core/web/register';

It installs the React DevTools backend before React mounts, keeps Vite Fast Refresh working, and leaves an existing complete DevTools hook alone. It also records the page's fetch and XHR calls until Network starts, at most the latest 100, and Network then lists them with the rest. Its server entry does nothing, and it doesn't open a Desktop connection. In a production build it runs only in browsers where FloatingDevTools has rendered in the past week, so visitors who never see Buoy are unaffected. Production builds has the details.

The right place for the import depends on the framework. Frameworks covers Next.js, React Router and TanStack Start.

Mounting

With the Vite plugin, mount BuoyDevTools:

tsx
import { BuoyDevTools } from '@buoy-gg/core/web/auto';

export function App() {
  return (
    <>
      {/* your app */}
      <BuoyDevTools licenseKey={import.meta.env.VITE_BUOY_KEY} />
    </>
  );
}
import { BuoyDevTools } from '@buoy-gg/core/web/auto';

export function App() {
  return (
    <>
      {/* your app */}
      <BuoyDevTools licenseKey={import.meta.env.VITE_BUOY_KEY} />
    </>
  );
}

BuoyDevTools shows Buoy in dev builds. In a Capacitor app, it uses the phone app's debug flag. Release builds don't load Buoy's tools or menu. They still load the early hook. It stays off unless Buoy ran in that browser in the last week. Set enabled to choose for yourself. It takes the same props as FloatingDevTools. Pass askBuoy="hosted" for hosted Ask Buoy. Mount it inside the providers your tools read.

Without the Vite plugin

Register the modules you use. The host finds their presets, mounts their capture and overlays, and registers their actions:

tsx
'use client';

import { useEffect } from 'react';
import { FloatingDevTools } from '@buoy-gg/core/web';
import * as network from '@buoy-gg/network/web';
import * as storage from '@buoy-gg/storage/web';
import * as zustand from '@buoy-gg/zustand/web';
import { useCounterStore } from './stores';

const modules = { network, storage, zustand };

export function DevTools({ licenseKey }: { licenseKey: string }) {
  useEffect(() => zustand.watchStores({ counter: useCounterStore }), []);
  return <FloatingDevTools modules={modules} licenseKey={licenseKey} />;
}
'use client';

import { useEffect } from 'react';
import { FloatingDevTools } from '@buoy-gg/core/web';
import * as network from '@buoy-gg/network/web';
import * as storage from '@buoy-gg/storage/web';
import * as zustand from '@buoy-gg/zustand/web';
import { useCounterStore } from './stores';

const modules = { network, storage, zustand };

export function DevTools({ licenseKey }: { licenseKey: string }) {
  useEffect(() => zustand.watchStores({ counter: useCounterStore }), []);
  return <FloatingDevTools modules={modules} licenseKey={licenseKey} />;
}

Mount the host inside the app's providers, behind a development check or, in production, a check for the users who should see it. Keep modules outside the component so it stays stable. Use keys that match the package names, such as 'react-query', 'route-events' and 'time-machine'. Notifications uses 'push-notifications'. When you pass modules, Buoy loads only those tools, even with the plugin.

A tool that needs callbacks from your app is configured as a preset and passed through apps={[createWebTool(preset)]}. It replaces the module's default preset with the same ID. Custom tools keep their modal metadata and sync adapter.

Sign in

A Free or Pro account is required. For Vite, run this from the app directory and finish the browser sign-in:

bash
npx --package=@buoy-gg/core buoy login
npx --package=@buoy-gg/core buoy login

The CLI writes a dev token as VITE_BUOY_KEY in .env.development.local. Restart the dev server. Then pass licenseKey={import.meta.env.VITE_BUOY_KEY}. Other tools set it in their own env setup. The token works in dev builds for 30 days. A live site uses Sign in with Buoy instead, with no key in the build. Account checks and plan limits still apply. Keys still work if you'd rather use one.

Tool setup

Each tool's own page is shared with React Native. This is what changes in the browser.

ToolBrowser source and setup
NetworkThe page's fetch and XHR calls, with request details, GraphQL metadata, response overrides, conditions, pins and saved requests. A standalone collector can call startNetworkCapture() and its cleanup function.
StoragelocalStorage and sessionStorage, with key editing, captured writes, undo and snapshot restore.
ZustandRegister live stores with watchStores. Actions stay callable after edits and restores.
JotaiRegister atoms with watchAtoms(store, atoms), using the app's store.
React QueryMount inside the existing QueryClientProvider. Cache observation, edits and snapshots use that client.
ReduxMount inside the existing Redux provider. Capture starts with the host. To let Time Machine restore the store, add buoyDevtoolsEnhancer from @buoy-gg/redux/web to its enhancers.
RoutesHistory API and hash navigation. List known paths with registerBrowserRoutes(paths) and connect your router with registerBrowserRouter(adapter), shown below.
EnvPass public values with setRemoteEnv(values) and required keys through the host's requiredEnvVars. Buoy can't list values a bundler replaced at build time.
Console, EventsBrowser JavaScript logs, and events from the registered tools.
SentryPass sentryGetClient={Sentry.getClient} from the app's browser SDK. Buoy observes outgoing envelopes and supported diagnostic hooks. The Sentry package isn't on npm yet.
Time MachineThe same snapshots, restore actions and recording as native. Only registered sources take part.
Ask BuoyThe shared agent, using the registered tools' actions. Configure the model endpoint and account access as on native.
ImpersonateUse createImpersonateTool with the app's user-search callback. The page's fetch and XHR calls get the configured header.
Perf Monitor, JS TopFrame rate, JS heap where the browser exposes it, long tasks and measured JavaScript callbacks. CPU, memory and thermal readings have no browser equivalent.
Highlight UpdatesReact render tracking and DOM measurement, through the early DevTools hook.
Image OverlayMatches data-testid="image-target:Name" elements or places a free overlay. Clipboard access follows browser permissions.
AssetsResources the page loaded, plus a build manifest for files that haven't loaded. See below.
Scene (Beta)Use @buoy-gg/three/web in apps with three. Call registerThree({ renderer, scene, camera, loadingManager }). Call its cleanup on scene swap and hot reload. Add Perf Monitor for counts, and Assets and Events for loads.

Debug Borders and Images also have browser builds; their pages cover the web setup. TV Remote and Focus Inspector observe browser keyboard and DOM focus events, which is not the same as a TV's native focus engine.

Routers

For React Router, Next.js or another router, give Buoy its real navigation methods:

ts
import { registerBrowserRouter } from '@buoy-gg/shared-ui/web/utils';

const cleanup = registerBrowserRouter({
  push: path => router.push(path),
  replace: path => router.replace(path),
  back: () => router.back(),
});
import { registerBrowserRouter } from '@buoy-gg/shared-ui/web/utils';

const cleanup = registerBrowserRouter({
  push: path => router.push(path),
  replace: path => router.replace(path),
  back: () => router.back(),
});

Register the adapter for the router's lifetime and call cleanup when it goes away. Without an adapter, Buoy changes same-origin browser history directly. History capture starts when Buoy mounts, because browsers don't expose the history stack from before that.

Assets that haven't loaded

The Vite plugin builds an inventory without downloading each asset in the browser:

ts
// vite.config.ts
import { buoy } from '@buoy-gg/core/vite';
import { buoyAssets } from '@buoy-gg/assets/vite';
export default { plugins: [buoy(), buoyAssets()] };
// vite.config.ts
import { buoy } from '@buoy-gg/core/vite';
import { buoyAssets } from '@buoy-gg/assets/vite';
export default { plugins: [buoy(), buoyAssets()] };

With buoy() in the list, Buoy loads the list for you. Without it, load the list in your dev setup:

ts
import { loadBrowserAssetManifest } from '@buoy-gg/assets/web';
await loadBrowserAssetManifest('/buoy-assets.json');
import { loadBrowserAssetManifest } from '@buoy-gg/assets/web';
await loadBrowserAssetManifest('/buoy-assets.json');

In production builds the manifest includes emitted assets and media in public. During Vite development it lists public media, and source assets show up as the page requests them. Respect your app's base path when loading the manifest. Other bundlers can generate an array of { url, bytes?, hash?, width?, height? } and pass it to registerBrowserAssetManifest.

Desktop and MCP

Install @buoy-gg/external-sync and add 'external-sync': externalSync to modules, importing the namespace from @buoy-gg/external-sync/web. The browser publishes the same tool capabilities and handles the same actions as a phone. Desktop and MCP keep their own account requirements.

In development the default broker is the page's hostname on port 42831, which is your machine even when you open the page from a phone on the same Wi-Fi. For another host, pass externalSync={{ socketURL: 'http://192.168.1.20:42831' }}. externalSync={false} turns the connection off. headless keeps capture and remote actions running without the floating menu.

Production builds connect only with a real Pro license and externalSync.enableInRelease, and default to http://localhost:42831, the Buoy Desktop on the admin's own machine. The first time a page from your production site connects, Buoy Desktop asks whether to allow that site. Production builds refuse plain http:// to another machine unless you also set allowInsecureNetwork, and they refuse Impersonate's user search and start actions unless you pass releaseActions: ['impersonate']. See Release builds.

Browser boundaries

  • Network capture covers the current page's fetch and XHR calls, starting when the early hook loads, or when the host mounts if you skip the hook. Other frames, workers, WebSockets and browser-internal traffic need separate collectors.
  • CORS decides which headers and bodies are readable. Cross-origin resource sizes and timings may need Timing-Allow-Origin, and Buoy reports them as unavailable rather than guessing. See MDN's Resource Timing guide.
  • Storage capture sees method calls and browser storage events. Direct property assignments, IndexedDB, cookies and native secure storage are separate APIs.
  • Images sees DOM image elements. CSS backgrounds and Canvas drawings don't have the same lifecycle. Cache bypass changes the image URL, since page JavaScript can't clear the browser's HTTP cache.
  • Keep device testing for native rendering, gestures, native modules, secure storage and hardware measurements.
  • Load the dev tools separately from your public bundle, and import only the tools you use. The full suite includes a lot of UI and images.