docs

Installation

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.

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.

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 early DevTools hook

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

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'.

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.

License key

A Free or Pro account key 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 VITE_BUOY_KEY to .env.development.local for a Free account or .env.local for a paid one. Restart the dev server, then pass licenseKey={import.meta.env.VITE_BUOY_KEY}. Other bundlers supply the key through their own environment configuration. Account checks, feature limits and development-only restrictions still apply.

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.

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 { buoyAssets } from '@buoy-gg/assets/vite';
export default { plugins: [buoyAssets()] };
// vite.config.ts
import { buoyAssets } from '@buoy-gg/assets/vite';
export default { plugins: [buoyAssets()] };

Load it from your development 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.