Ferret
Modern HTTP inspector for Flutter — zero setup, production-safe, raw visibility.
See everything. Ship nothing you didn't mean to.
Ferret captures Dio, package:http, and dart:io traffic into a Material 3 inspector: floating bubble, call list, raw detail, and cURL / HAR export.
Why Ferret
Most Flutter HTTP inspectors are debug-only. They are not designed to run safely in release builds.
Ferret is different:
| Typical inspectors | Ferret | |
|---|---|---|
| Debug / profile | On | On by default |
| Release builds | Usually unavailable or unsafe | Off by default (true no-op) |
| Opt-in release mode | Rare / not supported | Supported via enableInRelease: true |
| Release warning | Often missing | Always on — console banner + permanent red bubble border (cannot be disabled) |
| Data in-app | Often masked | Full raw bodies & headers |
| Setup | Wire every client | Ferret.install() + builder: Ferret.builder |
Install
dependencies:
ferret: ^0.2.1
flutter pub get
Quick start
import 'package:ferret/ferret.dart';
import 'package:flutter/material.dart';
void main() {
Ferret.install();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: Ferret.navigatorKey,
builder: Ferret.builder, // floating bubble (center-right by default)
home: const HomePage(),
);
}
}
MaterialApp.router / GoRouter
Wire Ferret's key to the router (same idea as MaterialApp.navigatorKey):
void main() {
Ferret.install();
runApp(const MyApp());
}
final _router = GoRouter(
navigatorKey: Ferret.navigatorKey,
routes: [
GoRoute(path: '/', builder: (_, __) => const HomePage()),
],
);
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: _router,
builder: Ferret.builder,
);
}
}
If your app already owns a GlobalKey<NavigatorState>, pass it in:
Ferret.install(config: FerretConfig(navigatorKey: yourNavKey));
// GoRouter(navigatorKey: yourNavKey, …)
Capture traffic
// Dio — recommended
final dio = Ferret.createDio();
// or attach to an existing instance:
dio.interceptors.add(Ferret.dioInterceptor);
// package:http
final client = Ferret.wrapClient(http.Client());
// dart:io HttpClient — captured automatically after install (mobile/desktop)
Ferret.dioInterceptor and Ferret.wrapClient() are order-independent: you
can create your Dio / http client before Ferret.install() (for example in a
DI container) and calls are captured once Ferret is installed and active.
Platform support
| Platform | Dio | package:http | dart:io |
|---|---|---|---|
| Android / iOS / macOS / Windows / Linux | ✓ | ✓ | ✓ |
| Web | ✓ | ✓ | — (not available) |
On web, use Ferret.createDio() or Ferret.wrapClient() — the inspector UI and HAR/cURL export work the same.
Configuration
Ferret.install(
config: const FerretConfig(
enabled: true, // master switch (debug/profile)
enableInRelease: false, // set true only when you intentionally need release
maxEntries: 500, // ring buffer size
captureBody: true,
maxBodyBytes: 256 * 1024, // per body; null = unlimited
clients: {
HttpClientType.dio,
HttpClientType.http,
HttpClientType.dartIo,
},
slowThreshold: Duration(seconds: 2),
// navigatorKey: yourNavKey, // only if GoRouter / app already owns a key
// onOpenUrl: (url) => launchUrl(url), // enables "Open" in the link sheet
),
);
Bodies larger than maxBodyBytes are cut at a character boundary; the
inspector shows a "Truncated" notice and sizes still report the full body.
Links and image previews
URLs in headers and bodies are tappable. Tapping one opens a sheet with icon
buttons: copy, preview (eye, image URLs only) and open (only when onOpenUrl
is set). A Links tab lists every unique URL in a call, grouped by where it
appeared. image/* response bodies render as images.
Headers and body cards have a chevron to collapse long sections.
Ferret has no url_launcher dependency; wire it yourself:
import 'package:url_launcher/url_launcher.dart';
FerretConfig(
onOpenUrl: (url) => launchUrl(url, mode: LaunchMode.externalApplication),
)
Security note: image previews make a real network request from the device, but only when you tap the preview (eye) icon. The request does not send the original call's auth headers, so authenticated images may fail (the HTTP status is shown). With
enableInRelease: truethis also applies to release builds: only enable it for builds you trust.
Debug and release behavior
| Mode | Behavior |
|---|---|
| Debug / Profile | On when enabled: true (default) |
| Release | Fully off unless enableInRelease: true |
Release + enableInRelease: true |
On, with a loud console warning and a permanent red border around the bubble |
When disabled, Ferret does not intercept, store, or render anything.
Floating bubble
- Count button (48×48), default center-right — drag to move, snaps to the nearer edge.
- Remembers position after you close the inspector.
- Flashes red briefly when a new failed call arrives.
- Tap opens the inspector; long-press hides until hot reload / hot restart.
- Hidden while the inspector is open.
Features
- Floating count button → full inspector
- Call list with method, path, host, status, duration, size
- Detail tabs: Overview · Request · Response · Links · Error
- Tappable URLs, link sheet, tap-to-load image previews
- Body size cap (
maxBodyBytes) to bound memory - Search & filters (method, failed, slow)
- Copy as cURL (per call)
- Share / copy session as HAR
Repository
https://github.com/darkmintis/ferret
Example
git clone https://github.com/darkmintis/ferret.git
cd ferret
fvm use 3.44.1
cd example
flutter run
API surface
Ferret.install(config: ...);
Ferret.navigatorKey
Ferret.builder
Ferret.createDio([options])
Ferret.dioInterceptor
Ferret.wrapClient(client)
Ferret.openDashboard()
Ferret.clear()
Ferret.toCurl(entry)
Ferret.toHar(redact: false)
Ferret.shareSession(redact: false)
Ferret.shareCurl(entry)
Ferret.isActive
Requirements
- Flutter
>=3.44.0 - Dart
^3.12.1
License
MIT © Darkmintis
Libraries
- ferret
- Ferret — modern HTTP inspector for Flutter.