
The BuoyDevTools widget is the entry point for Buoy on Flutter — the analog of React Native's FloatingDevTools. It wraps your app and renders a draggable floating button that opens a menu containing all your registered debugging tools.
This browser example runs the menu over a mock app. Drag the grip to reposition it and try the available controls.
BuoyDevTools bubble and dial are a 1:1 port of it. Step through the tour, or just start dragging and tapping.import 'package:buoy/buoy.dart';
MaterialApp(
builder: (context, child) => BuoyDevTools(
licenseKey: 'YOUR_LICENSE_KEY',
child: child ?? const SizedBox.shrink(),
),
)
import 'package:buoy/buoy.dart';
MaterialApp(
builder: (context, child) => BuoyDevTools(
licenseKey: 'YOUR_LICENSE_KEY',
child: child ?? const SizedBox.shrink(),
),
)
The buoy umbrella explicitly registers its bundled tools on mount. Don't have a key yet? Grab one at buoy.gg/pricing.
Mount it from MaterialApp.builder (or MaterialApp.router) so it sits above your Navigator and survives every route change.
| Prop | Type | Default | Notes |
|---|---|---|---|
child | Widget | required | Your app (usually the MaterialApp builder child) |
licenseKey | String? | null | Free or Pro account key; widget remains debug-only |
deviceName | String? | auto | Label in Buoy Desktop / MCP. Default: 'Flutter App (ios · 2c1d)' — the app, the platform, and the last 4 of the install id |
deviceId | String? | auto | Leave unset: each install mints flutter-app-ios-<8 hex> once and keeps it. If you pin one it MUST be unique per device — two devices under one id look like one device to the broker |
socketUrl | String? | auto | LAN broker URL for physical devices |
tools | List<BuoyTool> | [] | Extra custom tools beyond self-registered ones |
React Native discovers tools by optional-require; Dart has no equivalent, so Flutter tools register explicitly — but with the umbrella package you never write the call yourself:
Umbrella — package:buoy calls the registration functions for its bundled tool list when BuoyDevTools mounts. Adding a separate dependency does not extend that list. Install and it shows up:
flutter pub add buoy
flutter pub add buoy
À la carte — depend on the individual packages instead, call registerBuoyNetwork() (etc.) yourself before runApp, and mount BuoyDevTools from buoy_core.
A few tools still need one app-owned hook — the router for Routes, a ProviderScope observer for Riverpod, BuoyImage for Images, a search callback for Impersonate. The umbrella can't supply those, so each tool page notes its one line.
Need something specific to your app? Pass your own BuoyTools via the tools prop (or Buoy.registerTool before mount):
import 'package:buoy/buoy.dart';
import 'package:flutter/material.dart';
MaterialApp(
builder: (context, child) => BuoyDevTools(
tools: [
BuoyTool(
id: 'flags',
name: 'Flags',
color: const Color(0xFF34D399),
icon: (size, color) => Icon(Icons.flag, size: size, color: color),
screenBuilder: (context) => const FeatureFlagDebugger(),
),
],
child: child ?? const SizedBox.shrink(),
),
)
import 'package:buoy/buoy.dart';
import 'package:flutter/material.dart';
MaterialApp(
builder: (context, child) => BuoyDevTools(
tools: [
BuoyTool(
id: 'flags',
name: 'Flags',
color: const Color(0xFF34D399),
icon: (size, color) => Icon(Icons.flag, size: size, color: color),
screenBuilder: (context) => const FeatureFlagDebugger(),
),
],
child: child ?? const SizedBox.shrink(),
),
)
See Custom Tools for the full schema and desktop-sync adapters.
The floating button can be dragged anywhere on screen. It remembers its position between sessions, so it stays where your team likes it, and it tucks out of the way on its own while the dial or a tool is open. Tools you minimize dock above it and come back with a tap.
BuoyDevTools is also the source of truth for Buoy's other surfaces. The same tools you see in the menu sync out over a local broker, so once this is set up you can — with no extra code — also:
Simulators and emulators connect automatically. Physical devices take one socketUrl pointing at your machine:
BuoyDevTools(
socketUrl: 'http://192.168.1.20:42831',
child: child ?? const SizedBox.shrink(),
)
BuoyDevTools(
socketUrl: 'http://192.168.1.20:42831',
child: child ?? const SizedBox.shrink(),
)
The current sync target and connection state show up in the menu's Settings tab under desktop sync.
BuoyDevTools renders your child and nothing else outside a debug build — no button, no dial, no sync, no capture. There is no flag to change that on Flutter yet, This describes initialization managed by the widget; separately initialized code has its own behavior.
Two React Native options have no Flutter counterpart today:
environment — the RN floating button prints an environment badge (local, dev, staging, qa, prod). The Flutter bubble doesn't.headless and release-build sync — RN can mount sync-only with no on-device UI, and can opt a release build into sync with externalSync.enableInRelease plus a Pro license. Flutter's debug-only gate means neither applies yet.Both are on the Flutter roadmap.