# Screenshots | Sentry for Flutter

Sentry makes it possible to automatically take a screenshot and include it as an [attachment](https://docs.sentry.io/platforms/dart/guides/flutter/enriching-events/attachments.md) when a user experiences an error, an exception or a crash.

This feature is only available for SDKs with a user interface, like the ones for mobile and desktop applications. It's also limited by whether taking a screenshot is possible or not. For example, in some environments, like native iOS, taking a screenshot requires the UI thread, which often isn't available in the event of a crash. Another example where a screenshot might not be available is when the event happens before the screen starts to load. So inherently, this feature is a best effort solution.

## [Enabling Screenshots](https://docs.sentry.io/platforms/dart/guides/flutter/enriching-events/screenshots.md#enabling-screenshots)

Because screenshots may contain [PII](https://docs.sentry.io/platforms/dart/guides/flutter/data-management/sensitive-data.md), they are an opt-in feature.

Enable screenshots by setting the `attachScreenshot` option to `true` and wrap your root widget with `SentryWidget`.

```dart
await SentryFlutter.init(
  (options) {

    options.attachScreenshot = true;

  },
  appRunner: () => runApp(

    SentryWidget(
      child: MyApp(),
    ),

  ),
);
```

## [Customize Screenshot Capturing](https://docs.sentry.io/platforms/dart/guides/flutter/enriching-events/screenshots.md#customize-screenshot-capturing)

By default, Flutter limits screenshot captures to once every 2 seconds to minimize performance impact. While this debounce interval cannot be changed, you can customize capture behavior by implementing the `beforeCaptureScreenshot` callback in `SentryFlutter.init`.

This callback gives you fine-grained control over screenshot captures based on event and hint data, allowing you to implement conditional logic.

Return `true` to capture the screenshot, or `false` to skip it.

The `shouldDebounce` flag is set to `true` when debounce is active, meaning it will block the screenshot from being taken. If you want to capture screenshots regardless of the debounce, you can ignore the `shouldDebounce` flag.

```dart
options.beforeCaptureScreenshot = (event, hint, shouldDebounce) async {
  // If shouldDebounce is active, skip capturing
  if (shouldDebounce) {
    return false;
  }
  // Capture screenshot if it's a fatal event
  return event.level == SentryLevel.fatal;
};
```

## [Redact Screenshots via `masking`](https://docs.sentry.io/platforms/dart/guides/flutter/enriching-events/screenshots.md#redact-screenshots-via-masking)

The masking feature is enabled by default for screenshots, but can be disabled or configured by adjusting the `options.privacy` option.

Modifying this parameter will also affect `masking` for<!-- --> <!-- -->[`Session Replay`](https://docs.sentry.io/platforms/dart/guides/flutter/session-replay.md).

Masking in the Sentry Flutter SDK is based on Widget *types*, e.g. `Image`, not the string representation of the type (i.e. we check whether a `widgetInstance` should be masked by checking `if (widgetInstance is Image)` instead of `if (widgetInstance.runtimeType == 'Image')`). This means we can ensure masking works regardless of obfuscation in release builds and also works for subclasses. This approach allows the SDK to automatically mask widgets that are part of the Flutter SDK itself. However, for third-party widgets, you need to manually configure the privacy settings to mask their content. Read more about [Third Party Widgets](https://docs.sentry.io/platforms/dart/guides/flutter/enriching-events/screenshots.md#third-party-widgets) below.

## [Privacy Configuration](https://docs.sentry.io/platforms/dart/guides/flutter/enriching-events/screenshots.md#privacy-configuration)

The following options can be configured in the `options.privacy` field of your Sentry Flutter SDK, in `SentryFlutter.init((options) { ... })`:

| Key                               | Type   | Default | Description                                                                                                                                                                                                                              |
| --------------------------------- | ------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| maskAllText                       | `bool` | `true`  | Mask all text content. Draws a rectangle of text bounds with text color on top. Currently `Text`, `EditableText` and `RichText` widgets are masked.                                                                                      |
| maskAllImages                     | `bool` | `true`  | Mask content of all images. Draws a rectangle of image bounds with image's dominant color on top. Currently `Image` widgets are masked.                                                                                                  |
| maskAssetImages                   | `bool` | `true`  | Mask asset images coming from the root asset bundle.                                                                                                                                                                                     |
| mask\<T extends Widget>()         | `void` | /       | Mask given widget type `T` (or subclasses of `T`). Note: masking rules are called in the order they're added so if a previous rule already makes a decision, this rule won't be called.                                                  |
| unmask\<T extends Widget>()       | `void` | /       | Unmask given widget type `T` (or subclasses of `T`). Note: masking rules are called in the order they're added so if a previous rule already makes a decision, this rule won't be called.                                                |
| maskCallback\<T extends Widget>() | `void` | /       | Provide a custom callback to decide whether to mask the widget of class `T` (or subclasses of `T`). Note: masking rules are called in the order they're added so if a previous rule already makes a decision, this rule won't be called. |

For example, you can explicitly mask or unmask widgets by type, or you can even have a callback to decide whether a specific widget instance should be masked:

```dart
options.privacy.mask<IconButton>();
options.privacy.unmask<Image>();
options.privacy.maskCallback<Text>(
    (Element element, Text widget) =>
        (widget.data?.contains('secret') ?? false)
            ? SentryMaskingDecision.mask
            : SentryMaskingDecision.continueProcessing);
```

If you find that data isn't being masked with the default settings, please let us know by creating a [GitHub issue](https://github.com/getsentry/sentry-dart/issues/new?template=BUG_REPORT.yml).

To disable masking for [`Screenshots`](https://docs.sentry.io/platforms/dart/guides/flutter/enriching-events/screenshots.md) and [`Session Replay`](https://docs.sentry.io/platforms/dart/guides/flutter/session-replay.md) (not to be used on applications with sensitive data):

```dart
options.privacy.maskAllText = false;
options.privacy.maskAllImages = false;
```

## [Third Party Widgets](https://docs.sentry.io/platforms/dart/guides/flutter/enriching-events/screenshots.md#third-party-widgets)

The Sentry Flutter SDK cannot automatically mask widgets from third party packages. You need to manually configure the privacy configuration to mask the content of these widgets.

For example, if you are using the [FlutterMap](https://pub.dev/packages/flutter_map) package, you need to add the following privacy configuration:

```dart
options.privacy.mask<FlutterMap>();
```

## [Viewing Screenshots](https://docs.sentry.io/platforms/dart/guides/flutter/enriching-events/screenshots.md#viewing-screenshots)

If one is available, you'll see a thumbnail of the screenshot when you click on a specific issue from the [**Issues**](https://demo.sentry.io/issues/) page.

Once you've clicked on the event ID of a specific issue, you'll be able to see an overview of all the attachments as well as associated events in the "Attachments" tab.
