TesterPayKit — Flutter SDK
Let real testers report bugs from inside your Flutter app. Testers shake the device or tap the floating button and file a report with the screenshot (annotated if they like), a screen recording or a spoken voice note. The SDK adds device and app state, the screens they went through, and crashes on its own. Reports, sessions and engaged testing minutes land in your TesterPayKit project, where you triage them and pay your testers.
You need a TesterPayKit project. The SDK sends everything to the TesterPayKit API and is tied to a project ID. Accounts are currently invite-only — to request access, write to business@moinsen.dev.
Install
flutter pub add testerpaykit
Quick start
Get your project ID from the TesterPayKit web console, or with the
tpk CLI:
tpk --json project create --name "My App" --bundle-id com.example.myapp.
Initialise the SDK before runApp — fail-safe, so a TesterPayKit
problem never takes your app down:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:testerpaykit/testerpaykit.dart';
const _projectId = String.fromEnvironment('TPK_PROJECT_ID');
Future<TesterPayKit?> initTesterPayKit() async {
if (_projectId.isEmpty) return null;
try {
return await TesterPayKit.initialize(
config: TesterPayKitConfig(
projectId: _projectId,
apiBaseUrl: 'https://api.testerpaykit.com',
enableTesterMode: true,
debugMode: kDebugMode,
),
);
} catch (e) {
debugPrint('TesterPayKit disabled: $e');
return null;
}
}
final _navigatorKey = GlobalKey<NavigatorState>();
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final tpk = await initTesterPayKit();
runApp(MyApp(tpk: tpk));
}
class MyApp extends StatelessWidget {
const MyApp({required this.tpk, super.key});
final TesterPayKit? tpk;
@override
Widget build(BuildContext context) {
final sdk = tpk;
return MaterialApp(
navigatorKey: _navigatorKey,
// Screen views drive the testers' engaged minutes.
navigatorObservers: [if (sdk != null) sdk.navigatorObserver],
builder: (context, child) {
final content = child ?? const SizedBox.shrink();
if (sdk == null) return content;
return TesterPayKitWrapper(
sdk: sdk,
navigatorKey: _navigatorKey,
child: content,
);
},
home: const HomeScreen(),
);
}
}
Run it with your project ID:
flutter run --dart-define=TPK_PROJECT_ID=<project-uuid>
Shake the device or tap the floating button to file a test report, then
check that it arrived: tpk bug list --bundle-id com.example.myapp.
Unpaid beta testing
For an unpaid beta, pass showCompensation: false to
TesterPayKitWrapper. The real tester controls remain available, while
the earnings estimate is hidden in both the menu and session-end screen.
This controls presentation only: ensure the project's server-side payout
rules and recruitment terms also match the intended unpaid test.
Testers
The TesterPayKit widget — floating button, shake and three-finger tap, crash prompt, onboarding and every tester screen — only appears for people with a confirmed DE/AT/CH mobile number and your approval. That holds in every build, TestFlight and beta included: everyone else sees nothing from TesterPayKit. Session telemetry keeps running.
Give people a way in from your own UI, e.g. a "Become a tester" entry in your settings:
final tpk = TesterPayKit.instance;
ValueListenableBuilder<TpkAccess>(
valueListenable: tpk.access,
builder: (context, access, _) => access == TpkAccess.ready
? const SizedBox.shrink()
: ListTile(
title: const Text('Become a tester'),
onTap: () => tpk.openVerification(context),
),
);
openVerification(context) asks for the invite code when the build has
none, opens the phone confirmation, and says so while your approval is
pending. Once access is ready, the button appears. The SDK checks
again on every app resume and after any refusal from the server, so a
revoked approval hides the widget. refreshAccess() re-checks on
demand. The invite code is remembered across launches; for your own
debug or CI builds, pass one with testerId: in TesterPayKitConfig.
GoRouter and other Router 2.0 setups
The navigator observer only sees Navigator 1.0 route pushes. With GoRouter, report screens yourself on every route change — otherwise engaged minutes, and with them your testers' payouts, come out too low:
TesterPayKit.instance.trackScreenView('settings');
AI test runs started from outside
A coordinator can start an AI test run from the web console ("Start on
device") or the terminal (tpk autotest --project <id>). An app built
with aiRemoteTrigger: true picks the run up and lets the model drive
it — nobody has to tap anything on the device:
TesterPayKitWrapper(
sdk: sdk,
aiRemoteTrigger: true, // off by default
// aiRemotePollSeconds: 15,
child: child,
)
While the app is in the foreground and no AI run is open, the wrapper
asks the API every 15 seconds whether a run waits for this project. A
run is handed to one app only. The overlay closes itself 3 seconds
after the run ends, and polling resumes. The model gets a screenshot of
the app on every step (areas wrapped in TesterSensitive stay masked);
the TesterPayKit button and the AI overlay are hidden from it.
Only enable this in builds you want remote-controlled, such as your own debug or test builds. It needs a tester token for the project (the invite code above).
More API
| Call | What it does |
|---|---|
trackScreenView(name) / trackTap() |
Manual screen and tap tracking. |
submitBugReport(description: …) |
File a report from code; returns a local id, uploads in the background. |
submitFeedback(content: …, rating: …) |
General feedback. |
completeTask(taskId) |
Mark a test task as done. |
checkForUnreportedCrashes(context) |
Offer to report a crash from the last run (only while access is ready). |
access / refreshAccess() / openVerification(context) |
Who sees the widget; see Testers. |
flushAndEndSession() |
Upload pending data and close the session. |
Platform support
Android and iOS.
Requirements
- Dart
^3.11.0 - Flutter
>=3.41.0
License
Commercial licence, not open source: you may use the SDK unmodified as a dependency of your own apps, commercial ones included, to connect them to the TesterPayKit service. Details in LICENSE.