Set Up User Feedback
Learn how to enable User Feedback in your Cocoa app.
User Feedback lets you collect feedback from anywhere in your app without waiting for an error event. On supported platforms, you can use Sentry's managed feedback form, or you can collect feedback in your own UI and send it with the User Feedback API.
If you use a self-hosted Sentry instance, use version 24.4.2 or higher for the managed feedback form. Lower versions may have limited functionality.
The managed feedback form is available for iOS and iPadOS apps that can use UIKit. SwiftUI apps can present the same form through Sentry's SwiftUI helpers.
The managed form presentation APIs are experimental and aren't available in app extensions.
- Set up Sentry in your app.
- Use the latest Sentry Cocoa SDK. The Swift presentation APIs require version 9.17.0 or newer.
- Configure User Feedback when starting the SDK.
The Objective-C snippets on this page use the SentryObjC API.
Set configureUserFeedback during SDK initialization. This closure installs the User Feedback integration, which is required for the automatic SentrySDK.feedback.show() API and for global shake and screenshot triggers. It also provides the global configuration used by SentrySDK.feedback.show(), SentrySDK.FeedbackForm, and .sentryFeedback(isPresented:).
import Sentry
SentrySDK.start { options in
options.dsn = "___PUBLIC_DSN___"
options.configureUserFeedback = { config in
config.configureForm = { form in
form.formTitle = "Report Feedback"
form.submitButtonLabel = "Send Feedback"
}
config.onSubmitSuccess = { data in
print("Feedback submitted: \(data)")
}
config.onSubmitError = { error in
print("Feedback failed: \(error)")
}
}
}
import Sentry
SentrySDK.start { options in
options.dsn = "___PUBLIC_DSN___"
options.configureUserFeedback = { config in
config.configureForm = { form in
form.formTitle = "Report Feedback"
form.submitButtonLabel = "Send Feedback"
}
config.onSubmitSuccess = { data in
print("Feedback submitted: \(data)")
}
config.onSubmitError = { error in
print("Feedback failed: \(error)")
}
}
}
@import SentryObjC;
[SentryObjCSDK startWithConfigureOptions:^(SentryObjCOptions *options) {
options.dsn = @"___PUBLIC_DSN___";
options.configureUserFeedback = ^(SentryObjCUserFeedbackConfiguration *config) {
config.configureForm = ^(SentryObjCUserFeedbackFormConfiguration *form) {
form.formTitle = @"Report Feedback";
form.submitButtonLabel = @"Send Feedback";
};
config.onSubmitSuccess = ^(NSDictionary<NSString *, id> *data) {
NSLog(@"Feedback submitted: %@", data);
};
config.onSubmitError = ^(NSError *error) {
NSLog(@"Feedback failed: %@", error);
};
};
}];
For all configuration fields, see Configure User Feedback.
With global configuration, the SDK tracks the active managed form and won't present another form from SDK-managed presentation paths while one is already open.
If you create SentrySDK.FeedbackForm or SentrySDK.FeedbackFormView directly without global configuration, the form uses default settings plus any per-presentation configuration you provide.
Choose between programmatic presentation APIs and automatic triggers based on how much control your app needs:
- Use
SentrySDK.feedback.show()when the SDK can choose the presenter from the active scene. - Use the UIKit or SwiftUI APIs when your app needs exact control over the presenting view controller, scene, window, or sheet.
- Use shake and screenshot triggers when the form should open from a global user action.
Use SentrySDK.feedback.show() when you want the SDK to find a suitable view controller in the active scene and present the form for you.
SentrySDK.feedback.show { config in
config.configureForm = { form in
form.formTitle = "Feedback for this screen"
}
}
SentrySDK.feedback.show { config in
config.configureForm = { form in
form.formTitle = "Feedback for this screen"
}
}
[[SentryObjCSDK feedback] showWithConfigure:^(SentryObjCUserFeedbackConfiguration *config) {
config.configureForm = ^(SentryObjCUserFeedbackFormConfiguration *form) {
form.formTitle = @"Feedback for this screen";
};
}];
You can also pass a UIImage screenshot:
SentrySDK.feedback.show(screenshot: screenshot)
SentrySDK.feedback.show(screenshot: screenshot)
[[SentryObjCSDK feedback] showWithScreenshot:screenshot];
The SDK chooses a presenting view controller from the foreground-active scene when possible, with a window fallback for apps that don't use scenes. It only presents when that view controller isn't already presenting or transitioning.
If your app needs exact scene, window, or presenting view controller control, use the UIKit or SwiftUI APIs below to present the form yourself.
Use SentrySDK.FeedbackForm when your app should decide which view controller presents the form.
let form = SentrySDK.FeedbackForm(screenshot: screenshot) { config in
config.configureForm = { form in
form.formTitle = "Report Feedback"
form.submitButtonLabel = "Send Feedback"
}
}
present(form, animated: true)
let form = SentrySDK.FeedbackForm(screenshot: screenshot) { config in
config.configureForm = { form in
form.formTitle = "Report Feedback"
form.submitButtonLabel = "Send Feedback"
}
}
present(form, animated: true)
UIViewController *form = [SentryObjCFeedbackForm
viewControllerWithScreenshot:screenshot
configure:^(SentryObjCUserFeedbackConfiguration *config) {
config.configureForm = ^(SentryObjCUserFeedbackFormConfiguration *formConfig) {
formConfig.formTitle = @"Report Feedback";
formConfig.submitButtonLabel = @"Send Feedback";
};
}];
[self presentViewController:form animated:YES completion:nil];
Use .sentryFeedback(isPresented:) when a SwiftUI state binding should control the form presentation.
import Sentry
import SwiftUI
struct SettingsView: View {
@State private var isFeedbackPresented = false
var body: some View {
Button("Send Feedback") {
isFeedbackPresented = true
}
.sentryFeedback(isPresented: $isFeedbackPresented) { config in
config.configureForm = { form in
form.submitButtonLabel = "Send Feedback"
}
}
}
}
import Sentry
import SwiftUI
struct SettingsView: View {
@State private var isFeedbackPresented = false
var body: some View {
Button("Send Feedback") {
isFeedbackPresented = true
}
.sentryFeedback(isPresented: $isFeedbackPresented) { config in
config.configureForm = { form in
form.submitButtonLabel = "Send Feedback"
}
}
}
}
If you already manage your own presentation container, embed SentrySDK.FeedbackFormView in a SwiftUI sheet:
import Sentry
import SwiftUI
struct SettingsView: View {
@State private var isFeedbackPresented = false
var body: some View {
Button("Send Feedback") {
isFeedbackPresented = true
}
.sheet(isPresented: $isFeedbackPresented) {
SentrySDK.FeedbackFormView { config in
config.configureForm = { form in
form.submitButtonLabel = "Send Feedback"
}
}
}
}
}
import Sentry
import SwiftUI
struct SettingsView: View {
@State private var isFeedbackPresented = false
var body: some View {
Button("Send Feedback") {
isFeedbackPresented = true
}
.sheet(isPresented: $isFeedbackPresented) {
SentrySDK.FeedbackFormView { config in
config.configureForm = { form in
form.submitButtonLabel = "Send Feedback"
}
}
}
}
}
You can also open the managed form from a device shake or immediately after the user takes a screenshot. These options are global and only apply when set in options.configureUserFeedback.
SentrySDK.start { options in
options.dsn = "___PUBLIC_DSN___"
options.configureUserFeedback = { config in
config.useShakeGesture = true
config.showFormForScreenshots = true
}
}
SentrySDK.start { options in
options.dsn = "___PUBLIC_DSN___"
options.configureUserFeedback = { config in
config.useShakeGesture = true
config.showFormForScreenshots = true
}
}
[SentryObjCSDK startWithConfigureOptions:^(SentryObjCOptions *options) {
options.dsn = @"___PUBLIC_DSN___";
options.configureUserFeedback = ^(SentryObjCUserFeedbackConfiguration *config) {
config.useShakeGesture = YES;
config.showFormForScreenshots = YES;
};
}];
In version 9.25.0 or later, you can also enable or disable the shake gesture at runtime, for example once an asynchronously resolved feature flag or user role decides whether to offer feedback. This requires the User Feedback integration to be configured via configureUserFeedback; otherwise the calls are a no-op. Call these methods from the main thread.
// Start detecting shake gestures to open the feedback form
SentrySDK.feedback.enableOnShake()
// Stop detecting shake gestures
SentrySDK.feedback.disableOnShake()
// Start detecting shake gestures to open the feedback form
SentrySDK.feedback.enableOnShake()
// Stop detecting shake gestures
SentrySDK.feedback.disableOnShake()
// Start detecting shake gestures to open the feedback form
[[SentryObjCSDK feedback] enableOnShake];
// Stop detecting shake gestures
[[SentryObjCSDK feedback] disableOnShake];
The managed feedback form works with Session Replay. When the form opens, the Replay SDK buffers up to 30 seconds of the user's session. If the user submits feedback, the replay is sent with the feedback so you can see what led up to it.
The Sentry-managed User Feedback widget, showWidget(), hideWidget(), configureWidget, and customButton configuration are deprecated and will be removed in v10.
If you're migrating from customButton, keep your existing button and replace the SDK configuration with a regular button action. If you're migrating from the widget, add your own button, menu item, or other entry point and call the same presentation API. Your global configureUserFeedback form, theme, and hook configuration still applies.
@IBAction private func feedbackButtonTapped(_ sender: UIButton) {
SentrySDK.feedback.show()
}
@IBAction private func feedbackButtonTapped(_ sender: UIButton) {
SentrySDK.feedback.show()
}
- (IBAction)feedbackButtonTapped:(UIButton *)sender {
[[SentryObjCSDK feedback] show];
}
Use the User Feedback API when you collect feedback with your own UI. The SDK sends the feedback to Sentry, so you don't need to build the HTTP request yourself.
You can optionally pass an associatedEventId to connect feedback to an error event.
import Sentry
let feedback = SentryFeedback(
message: "The settings screen is confusing.",
name: "Ada",
email: "ada@example.com",
source: .custom,
associatedEventId: nil,
attachments: nil
)
SentrySDK.capture(feedback: feedback)
import Sentry
let feedback = SentryFeedback(
message: "The settings screen is confusing.",
name: "Ada",
email: "ada@example.com",
source: .custom,
associatedEventId: nil,
attachments: nil
)
SentrySDK.capture(feedback: feedback)
@import SentryObjC;
[SentryObjCSDK captureFeedbackWithMessage:@"The settings screen is confusing."
name:@"Ada"
email:@"ada@example.com"
source:SentryObjCFeedbackSourceCustom
associatedEventId:nil
attachments:nil];
Our documentation is open source and available on GitHub. Your contributions are welcome, whether fixing a typo (drat!) or suggesting an update ("yeah, this would be better").