All but React Native are in beta.
All but React Native are in beta.

Configure an authorized user-search API and attach the current impersonation headers to requests sent to your backend. Select a test user, verify the header in Network, then stop impersonation and confirm the header is removed.
Your backend must authenticate the operator and authorize the target user. Adding a header does not grant permission.

what your users see, in 72 secondsThe film shows the React Native tool.
This interactive tool uses fictional users. A real app needs a configured user-search and impersonation flow authorized by your backend.
flutter pub add buoy_impersonateUnlike other Buoy tools, Impersonate requires configuration because it needs to integrate with your user search API. Using the buoy umbrella? The dial entry is already registered — still call registerBuoyImpersonate(...) to wire search.
import 'package:buoy_impersonate/buoy_impersonate.dart';
registerBuoyImpersonate(
onSearchUsers: (query) async => api.searchUsers(query), // List<ImpersonateUser>
);
import 'package:buoy_impersonate/buoy_impersonate.dart';
registerBuoyImpersonate(
onSearchUsers: (query) async => api.searchUsers(query), // List<ImpersonateUser>
);
The snippets assume your app defines api.searchUsers and a Dio instance named dio. Add the interceptor only to the client for your authorized backend, not to a shared client that also sends requests to third-party services:
import 'package:dio/dio.dart';
dio.interceptors.add(InterceptorsWrapper(onRequest: (options, handler) {
options.headers.addAll(BuoyImpersonate.instance.impersonationHeaders);
handler.next(options);
}));
import 'package:dio/dio.dart';
dio.interceptors.add(InterceptorsWrapper(onRequest: (options, handler) {
options.headers.addAll(BuoyImpersonate.instance.impersonationHeaders);
handler.next(options);
}));
When impersonation is active, impersonationHeaders contains:
x-impersonate-user-id: user_123
x-impersonate-user-id: user_123
Your backend checks for this header and returns data for the specified user instead of the authenticated admin. Your backend decides what the headers mean — Buoy just makes toggling them one tap.
Security Note: Your backend should validate that the authenticated user has admin/impersonation permissions before honoring this header.
Search for users by email, name, or ID. Results display in a clean card format showing user details and metadata. When viewing an active impersonation, the card shows a control to stop directly from the search results.
Quick-switch between recently impersonated users. History persists across app restarts. Each history entry shows when the user was last impersonated.
Pass optional clearing callbacks so switching users clears stale client state:
| Callback | Typical use |
|---|---|
onClearReactQuery | Invalidate Riverpod/query-style caches |
onClearRedux | Reset a global store |
onClearAsyncStorage | Clear shared_preferences (preserve Buoy keys yourself if needed) |
onClearMMKV | Clear an MMKV / secure backend |
Unlike React Native, Flutter has no runtime auto-detection — “configured” means you passed the callback.
A floating banner appears when impersonation is active, to identify the selected user. Toggle it in Settings.
onSearchUsers should return List<ImpersonateUser>:
class ImpersonateUser {
final String id; // Sent in the impersonation header
final String? displayName; // Shown in UI (falls back to email, then id)
final String? email;
final String? avatarUrl;
final Map<String, Object?>? metadata;
}
class ImpersonateUser {
final String id; // Sent in the impersonation header
final String? displayName; // Shown in UI (falls back to email, then id)
final String? email;
final String? avatarUrl;
final Map<String, Object?>? metadata;
}
Customize the header key and related defaults:
registerBuoyImpersonate(
onSearchUsers: api.searchUsers,
defaults: const ImpersonateDefaults(
headerKey: 'x-admin-impersonate',
),
);
registerBuoyImpersonate(
onSearchUsers: api.searchUsers,
defaults: const ImpersonateDefaults(
headerKey: 'x-admin-impersonate',
),
);
Read the live map anywhere with BuoyImpersonate.instance.impersonationHeaders.
Register buoy_impersonate with your user-search callback, pick a user in the tool, and attach BuoyImpersonate.instance.impersonationHeaders in your HTTP client — a dio interceptor, for example. The state persists across launches and mirrors live to Buoy Desktop.
No. Dart has no global fetch to patch, so you decide where the headers go, and your backend decides what impersonation means and who is allowed to use it. Buoy is the switch, not the authority.