Redacted on the device
Tokens, JWTs, keys, card numbers and sensitive headers are scrubbed before anything is stored or uploaded. Passwords and fields you mark sensitive never leave the device.
Turn a failure you already track into a reproducible case, verify the fix, and keep it as a regression check. Capture the state, events, and environment when your app breaks — alongside Sentry or on its own.
See a real debugging workflow in action. No signup or SDK installation required.
Now in beta. Sign up with Google or GitHub to create your workspace. Get beta support ·
Capsule illustration · sample failure
The problem
QA sees a bug. The developer gets a screen recording and a description, then spends the afternoon guessing. The bugs that resist are the ones that depend on what the server returned, how long it took, or state left on the device from an earlier version.
A capsule carries exactly that context, so the failure comes back on the developer's simulator instead of in a meeting.
Works on my machine ≠ fixed
How it works
One capsule, from the failing build to your CI.
When your app fails, the SDK saves a capsule: the actions, HTTP responses with their timing, state and storage that led there. It is redacted on the device before upload.
BugCapsuleOverlay(child: child!)
The CLI checks out the exact commit and re-runs your real app on a simulator, an emulator or Chromium, feeding it the recorded inputs until the failure fires again.
bugcapsule run <id> -n 3
Replay against your working tree. Only when every attempt runs clean is the capsule FIXED. Keep it as a regression test in CI so the bug can't come back.
bugcapsule verify <id> -n 5
Dashboard · design preview
Inspect the timeline, state, events and network of a capsule, copy the replay and verify commands, and share it with your team.
Capsule e73f5ad0
Event 284 / 612
› onTap()
lib/screens/cart.dart:87
⌄ CartState (expanded)
{
"items": [ … ],
"price": null,
"subtotal": 84.0
}
Actions
Platforms
One capsule format, one bugcapsule CLI and one dashboard for all four. A capsule replays on the runtime that captured it.
Replays on iOS simulators and Android emulators
package:http (Retrofit, Chopper)REPRODUCED 3/3 FIXED 2/2
Replays on an emulator through instrumentation
REPRODUCED 3/3 FIXED 3/3
Replays on a simulator through XCUITest
REPRODUCED 3/3 FIXED 3/3
Replays in Chromium through Playwright
fetch and XMLHttpRequestREPRODUCED 3/3 FIXED 3/3
Results from each SDK's demo shop app against a local backend: Flutter on an iPhone 18 Pro simulator (iOS 27), Android on a Pixel 6 emulator (API 35), iOS on an iPhone 18 Pro simulator (iOS 27), web in Chromium 153.
Integrate
Add the SDK for your platform, start it first thing, attach your HTTP client and router, and pin the commit at build time.
# pubspec.yaml: one package, every integration
dependencies:
bug_capsule: 0.4.1-beta.5
// main.dart
import 'package:bug_capsule/bug_capsule.dart';
final bugCapsuleDio = BugCapsuleDio(rules: [
// Fields that differ on every run must not affect request matching.
EndpointRule('POST', '/api/checkout', ignoreBodyFields: {'timestamp', 'nonce'}),
]);
final bugCapsuleRouter = BugCapsuleGoRouter();
void main() => BugCapsule.init( // first line of main
config: const BugCapsuleConfig(
appVersion: '1.4.0',
appBuild: '52',
mode: CaptureMode.qa,
backend: BugCapsuleBackendConfig(url: 'https://bugcapsule.example.com', ingestKey: 'bci_...'),
),
integrations: [
BugCapsuleBloc(states: [ // or BugCapsuleRiverpod, BugCapsuleNotifiers, BugCapsuleGetx, BugCapsuleMobx
BugCapsuleState<CartBloc, CartState>(toJson: (s) => s.toJson(), fromJson: CartState.fromJson),
]),
bugCapsuleDio, // or BugCapsuleHttp for package:http and Chopper
bugCapsuleRouter,
BugCapsuleSharedPreferences(),
],
appRunner: () => runApp(const MyApp()),
);
// where you build Dio and GoRouter:
bugCapsuleDio.attach(dio);
final router = GoRouter(initialLocation: bugCapsuleRouter.initialLocation('/'), routes: [...]);
bugCapsuleRouter.attach(router);
// QA capture button:
MaterialApp.router(builder: (context, child) => BugCapsuleOverlay(child: child!), ...);
# Build with the commit pinned so replay can check out the same code
flutter build ipa --dart-define=BUGCAPSULE_GIT_SHA=$(git rev-parse HEAD) --dart-define=BUGCAPSULE_MODE=qa
// app/build.gradle.kts
android {
defaultConfig {
testInstrumentationRunner = "dev.bugcapsule.android.replay.BugCapsuleReplayRunner"
// The CLI passes -PbugcapsuleGitSha=<sha> so capsules carry the build's commit.
buildConfigField("String", "BUGCAPSULE_GIT_SHA", "\"${providers.gradleProperty("bugcapsuleGitSha").getOrElse("")}\"")
buildConfigField("String", "BUGCAPSULE_INGEST_KEY", "\"${providers.environmentVariable("BUGCAPSULE_INGEST_KEY").getOrElse("")}\"")
}
buildFeatures.buildConfig = true
}
dependencies {
implementation("io.github.bugcapsule:bugcapsule-android:0.4.1-beta.4")
implementation("io.github.bugcapsule:bugcapsule-okhttp:0.4.1-beta.4")
implementation("io.github.bugcapsule:bugcapsule-navigation:0.4.1-beta.4")
implementation("io.github.bugcapsule:bugcapsule-compose:0.4.1-beta.4") // if you use Compose
androidTestImplementation("io.github.bugcapsule:bugcapsule-android-replay:0.4.1-beta.4")
}
// Application.onCreate, first thing
BugCapsule.init(this,
BugCapsuleConfig.builder()
.mode(CaptureMode.QA)
.backend("https://bugcapsule.example.com", BuildConfig.BUGCAPSULE_INGEST_KEY) // bci_ only
.gitSha(BuildConfig.BUGCAPSULE_GIT_SHA)
.build(),
listOf(okHttp, navigation, BugCapsuleSharedPreferences.builder().allowFile("shop_prefs").build()))
// OkHttp: BugCapsule is the innermost application interceptor
OkHttpClient.Builder().addInterceptor(AuthInterceptor()).addInterceptor(okHttp.interceptor).build()
// Views and Compose: name actions, track text
BugCapsuleViews.onClick(checkoutButton, "cart.checkout") { viewModel.checkout() }
Button(onClick = rememberBugCapsuleAction("cart.checkout") { viewModel.checkout() }) { Text("Checkout") }
// Package.swift (or File → Add Package Dependencies… in Xcode)
.package(url: "https://github.com/BugCapsule/bug_capsule_ios_sdk.git", exact: "0.4.1-beta.2"),
.product(name: "BugCapsule", package: "bug_capsule_ios_sdk"), // app target
.product(name: "BugCapsuleXCTest", package: "bug_capsule_ios_sdk"), // UI test target
// First line of application(_:didFinishLaunchingWithOptions:) or App.init
import BugCapsule
BugCapsule.start(
BugCapsuleConfig(
mode: .qa,
backend: BugCapsuleBackendConfig(url: "https://bugcapsule.example.com", ingestKey: "bci_...")),
integrations: [
BugCapsuleURLSession(rules: [EndpointRule("POST", "/api/checkout", ignoreBodyFields: ["nonce"])]),
BugCapsuleUserDefaults(keys: ["cart_cache"], prefixes: ["settings."]),
BugCapsuleNavigation.shared,
BugCapsuleStateIntegration.shared,
])
// Networking: instrument the configuration your URLSession uses
let config = URLSessionConfiguration.default
BugCapsule.instrument(config)
// UIKit and SwiftUI: name actions, track text
Button("Checkout", action: BugCapsule.action("cart.checkout") { model.checkout() })
TextField("Coupon", text: BugCapsule.trackedText("cart.coupon", $model.coupon))
# Install the published beta packages
npm install @bugcapsule/web@0.4.0-beta.2 @bugcapsule/react@0.4.0-beta.2
npm install -D @bugcapsule/web-replay@0.4.0-beta.2 # only where you run bugcapsule run / verify
// main.ts: await init before rendering
import { BugCapsule, networkIntegration, historyIntegration, localStorageIntegration } from '@bugcapsule/web';
await BugCapsule.init({
appVersion: '1.4.0',
mode: 'qa',
gitSha: import.meta.env.VITE_BUGCAPSULE_GIT_SHA,
backend: { url: 'https://bugcapsule.example.com', ingestKey: 'bci_...' },
integrations: [
networkIntegration({ rules: [{ method: 'POST', path: '/api/checkout', ignoreBodyFields: ['nonce'] }] }),
historyIntegration(),
localStorageIntegration({ allowPrefixes: ['shop.'] }),
],
allowReplay: import.meta.env.DEV,
});
renderApp();
// React: actions, route events, error boundary, QA capture button
<button onClick={BugCapsule.action('cart.checkout', checkout)} data-bugcapsule-action="cart.checkout">
<BugCapsuleRouteListener /> <BugCapsuleErrorBoundary>…</BugCapsuleErrorBoundary> <BugCapsuleCaptureButton />
dart pub global activate bug_capsule 0.4.1-beta.5
bugcapsule login --url https://api.bugcapsule.com --token bcp_...
bugcapsule list # capsules on the backend
bugcapsule inspect <id|file|share-url> # failure, eligibility, network, timeline
bugcapsule run <id> -d "iPhone 18 Pro" -n 3 # replay on the capsule's commit
bugcapsule run <id> --runtime android -n 3 # flutter | android | ios | web, or from bugcapsule.json
bugcapsule verify <id> -n 5 # replay on your working tree
bugcapsule regress add <id> && bugcapsule regress run # exit 1 on failure, for CI
bugcapsule share <id> # expiring public link
bugcapsule capture --on | --off | --sample-rate 0.05 # kill switch and sampling
Safety and security
Capture runs inside your users' app, so the SDK is designed to fail quietly and stop on command.
Tokens, JWTs, keys, card numbers and sensitive headers are scrubbed before anything is stored or uploaded. Passwords and fields you mark sensitive never leave the device.
One command stops capture everywhere. Devices stop recording and delete queued captures, even offline. Sampling and size limits keep overhead bounded.
If capture fails, your app starts without it, and SDK errors never reach your handlers. Release builds can't enter replay mode.
Scoped ingest keys, expiring share links and per-project retention. Read how data is handled.
FAQ
Something missing? Ask us directly.
A bug replay tool for mobile and web apps. When your app fails, the SDK records what led there: user actions, network responses and their timing, state and storage. The bugcapsule CLI replays that recording against your real code so you can reproduce the bug, fix it and prove the fix.
Flutter, native Android (Kotlin or Java), native iOS (Swift, iOS 15+) and the web, with React helpers. Flutter apps replay on iOS simulators and Android emulators, native Android on an emulator, native iOS on a simulator, and web apps in Chromium.
Session replay shows a video of what the user saw, and crash reporting gives you a stack trace. BugCapsule re-runs your actual app with the recorded inputs, so the failure happens again on your machine, where you can debug it and verify the fix.
No. It works alongside Sentry or on its own. With Sentry connected, the reproduction and verification result appears on the Sentry issue. The Sentry link is available for web apps today.
That every requested verify attempt ran to the end as recorded and the original failure did not fire. One clean attempt and one error is INCONCLUSIVE, never FIXED.
Redaction runs on the device before anything is uploaded. You can exclude endpoints, turn off network bodies remotely and add your own redaction rules. Redaction is pattern based, so inspect real capsules before widening a rollout.
WebSockets, platform channels and third-party native SDKs such as Firebase, databases like SQLite or Core Data, and physical devices. Replay runs on simulators, emulators and Chromium. See the known limitations.
Create a workspace, add the SDK and replay your first bug today.
Beta support from the people who wrote the code: contact@bugcapsule.com. We reply within one business day.