BetaFeedbackKit turns vague TestFlight screenshots into structured, developer-ready reports without a backend or bug form. Testers do less work; you get their original words, one targeted follow-up, and relevant app context.
It can:
- show lightweight screenshot guidance
- ask at most one short follow-up question on-device
- add app, device, and developer-provided context
- copy the finished report for TestFlight
flowchart LR
A[Take a screenshot] --> B[Reply to the notification]
B --> C[Answer one optional follow-up]
C --> D[Paste the report into TestFlight]
If notifications or the on-device model are unavailable, the flow shortens instead of failing.
Add https://github.com/AndreasInk/BetaFeedbackKit in Xcode's package dependencies, or add it to Package.swift:
dependencies: [
.package(
url: "https://github.com/AndreasInk/BetaFeedbackKit",
branch: "main"
)
]Then add the BetaFeedbackKit library product to your app target.
- iOS 17+
- macOS 14+
- Swift 6.2+
- Xcode 27+ for Foundation Models, StateReporting, and MetricKit features
import SwiftUI
import BetaFeedbackKit
struct ContentView: View {
@State private var feedback = BetaContentViewModel(
allowsFeedbackPasteboardExport: true,
feedbackContextProvider: {
[
"screen": "checkout",
"recent_action": "Tapped Continue",
"domain_context": "Checkout reserves an item before payment and shows confirmation after the reservation succeeds."
]
},
feedbackClarificationMode: .onDevice,
feedbackNotificationMode: .onScreenshot
)
var body: some View {
CheckoutView()
.beta(viewModel: feedback)
.task { feedback.setup() }
}
}feedbackClarificationMode: .onDevice lets Apple's on-device model ask one follow-up in the feedback sheet. feedbackNotificationMode: .onScreenshot starts the notification flow after a screenshot. Both are opt-in.
Use feedbackContextProvider to include short, factual app context in the developer-facing
report. This context is not sent to the on-device model. Do not include personal data, secrets,
tokens, private URLs, or speculative causes.
- A short popover beside the screenshot preview shows the current screen or feature when supplied.
- Opening the system screenshot preview backgrounds the app; BetaFeedbackKit detects that transition and schedules the first notification for one second later.
- The first notification asks for one text reply.
- The on-device model may ask one short follow-up. After the tester answers or skips it, the report is complete.
- With pasteboard export enabled, the finished report is copied and the tester is guided back to TestFlight to paste it.
For example:
“Continue didn’t work.”
BetaFeedbackKit might ask:
“When you tapped Continue, did the screen stay the same, or did you see an error?”
The original answer, clarification, and app context are kept in the final report.
Your app owns UNUserNotificationCenter.delegate. Forward BetaFeedbackKit notifications from that delegate:
import BetaFeedbackKit
import UserNotifications
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
) {
if feedbackViewModel.handleNotificationResponse(
response,
completionHandler: completionHandler
) {
return
}
completionHandler()
}
func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler:
@escaping (UNNotificationPresentationOptions) -> Void
) {
completionHandler(
feedbackViewModel.notificationPresentationOptions(for: notification) ?? []
)
}
}Set the delegate during app launch and keep the same BetaContentViewModel available to it. Foreground banners require the willPresent forwarding shown above.
On older systems or without notification access, BetaFeedbackKit falls back to normal TestFlight sharing; if only the model is unavailable, it prepares the tester's first response without follow-ups.
- Analysis stays on-device with no backend, API key, account, or external model provider. Active conversations remain in the host app's
UserDefaultsfor up to 24 hours; an optional app-rendered screenshot stays in memory only. - Notification
userInfocontains routing IDs only, while visible questions may reflect supplied feedback or the optional in-memory screenshot. Analytics contain flow metadata, never tester responses; do not supply personal data, tokens, or private URLs as context.
feedbackScreenshotProvideradds an in-memory app image for on-device analysis..betaState(domain:state:metadata:)adds app state.feedbackDiagnosticsMode: .onDeviceadds privacy-filtered MetricKit evidence when available.onFeedbackPreparedandlatestFeedbackReportexpose the finished report.beta-feedbackandbeta-screenshot-tipare supported deep-link hosts throughhandleDeepLink(_:).startFeedbackNotificationConversation()starts the same flow from a debug menu or custom trigger.
Run swift test for deterministic coverage. Xcode 27 also runs the Apple Evaluations test, which scores clarification quality across text, conversation-history, and bundled screenshot fixtures using the on-device model.
MIT