# Using Custom Masking for Session Replay | Sentry for iOS

Before enabling Session Replay in production, verify your masking configuration to ensure no sensitive data is captured. Our default settings aggressively mask potentially sensitive data, but if you modify these settings or update UI frameworks or system SDKs, you must thoroughly test your application. If you find any masking issues or sensitive data that should be masked but isn't, please [create a GitHub issue](https://github.com/getsentry/sentry-cocoa/issues/new/choose) and avoid deploying to production with Session Replay enabled until the issue is resolved.

By default, our Session Replay SDK masks all text content, images, and user input. This helps ensure that no sensitive data will be exposed. You can also manually choose which parts of your app's data you want to mask by using the different options listed below.

## [Mask by View Class](https://docs.sentry.io/platforms/apple/guides/ios/session-replay/customredact.md#mask-by-view-class)

You can choose which type of view you want to mask or unmask by using the `maskedViewClasses` or `unmaskedViewClasses` options.

Custom masking only works with subclasses of `UIView`. You cannot mask or unmask `UIViewController` classes or other non-view types. If you need to mask content within a view controller, you should target the specific `UIView` instances instead.

Let's say you have a custom view that you want to mask and a `UILabel` subclass (which normally would be masked) that you don't want to mask. You can set the options like this:

```swift
options.sessionReplay.maskedViewClasses = [MyCustomView.self]
options.sessionReplay.unmaskedViewClasses = [MyCustomLabel.self]
```

## [Mask by View Instance](https://docs.sentry.io/platforms/apple/guides/ios/session-replay/customredact.md#mask-by-view-instance)

You can also choose to mask or unmask a specific view instance by using the replay API (`SentrySDK.replay`) or view extensions like this:

```swift
SentrySDK.replay.maskView(view: view)
SentrySDK.replay.unmaskView(view: label)
```

or

```swift
view.sentryReplayMask()
label.sentryReplayUnmask()
```

## [SwiftUI](https://docs.sentry.io/platforms/apple/guides/ios/session-replay/customredact.md#swiftui)

Because of the way SwiftUI is transformed into UIKit, it will often be over-masked. A modifier like `background` uses the same element as an `Image`. In order to control the SwiftUI masking process, you need to use the `sentryReplayUnmask` and/or `sentryReplayMask` modifiers.

In this example we want to show the message, but not the user name.

```swift
@Binding var user: String

var body: some View {
  VStack {
    Text("Hello")
      .sentryReplayUnmask()
    Text("\(user)")
  }
}
```

In this example, we need to unmask the VStack because its background element will be masked by default. To hide the username, we need to mask it.

```swift
@Binding var user: String

var body: some View {
  VStack {
    Text("Hello")
    Text("\(user)")
      .sentryReplayMask()
  }
  .background(.blue)
  .sentryReplayUnmask()
}
```

## [Ignore View Types from Subtree Traversal](https://docs.sentry.io/platforms/apple/guides/ios/session-replay/customredact.md#ignore-view-types-from-subtree-traversal)

Some view hierarchies contain layers that crash when accessed during traversal. For example, `CameraUI.ChromeSwiftUIView` on iOS 26+ contains `CameraUI.ModeLoupeLayer` instances that cause fatal errors when their `sublayers` property is accessed, resulting in crashes during session replay or screenshot capture.

To prevent these crashes, you can configure the SDK to skip traversing the subtree of specific view types while still redacting the view itself if needed.

### [Default Behavior](https://docs.sentry.io/platforms/apple/guides/ios/session-replay/customredact.md#default-behavior)

On iOS 26+, `CameraUI.ChromeSwiftUIView` is automatically included in the ignore set by default to prevent crashes. On other platforms or iOS versions, the ignore set is empty by default.

### [Adding View Types to Ignore List](https://docs.sentry.io/platforms/apple/guides/ios/session-replay/customredact.md#adding-view-types-to-ignore-list)

You can add custom view types to the ignore set using the `excludeViewTypeFromSubtreeTraversal` method:

```swift
options.sessionReplay.excludeViewTypeFromSubtreeTraversal("MyProblematicView")
```

The view type identifier uses **partial string matching** against the result of `type(of: view).description()`. This means if you exclude `"MyView"`, it will match `"MyApp.MyView"`, `"MyViewSubclass"`, `"Some.MyView.Container"`, etc. The view type identifier typically follows the format `"ModuleName.ClassName"`.

### [Removing View Types from Ignore List](https://docs.sentry.io/platforms/apple/guides/ios/session-replay/customredact.md#removing-view-types-from-ignore-list)

If you need to remove a view type from the ignore set (not recommended for default values), you can use:

```swift
options.sessionReplay.includeViewTypeInSubtreeTraversal("CameraUI.ChromeSwiftUIView")
```

The view type identifier uses **exact string matching** against the result of `type(of: view).description()`. This means `"MyApp.MyView"` will only match exactly `"MyApp.MyView"`, not `"MyApp.MyViewSubclass"`. This exact matching prevents accidental matches when removing view types from the ignore set.

Removing default values like `CameraUI.ChromeSwiftUIView` from the ignore set may cause crashes on iOS 26+. Only do this if you understand the risks and have tested thoroughly.

### [Dictionary Initialization](https://docs.sentry.io/platforms/apple/guides/ios/session-replay/customredact.md#dictionary-initialization)

You can also configure ignored view types when initializing `SentryReplayOptions` from a dictionary:

```swift
let replayOptions = SentryReplayOptions(dictionary: [
    "excludedViewClasses": ["MyCustomView", "AnotherProblematicView"],
    "includedViewClasses": ["CameraUI.ChromeSwiftUIView"]
])
```

* `excludedViewClasses`: An array of view type identifier patterns that use partial matching (contains)
* `includedViewClasses`: An array of view type identifiers that use exact matching to remove items from the excluded set

### [How It Works](https://docs.sentry.io/platforms/apple/guides/ios/session-replay/customredact.md#how-it-works)

The final set of excluded view types is computed using the formula: **Default View Classes + Excluded View Classes - Included View Classes**

* Default view classes are defined internally by the SDK (e.g., `CameraUI.ChromeSwiftUIView` on iOS 26+)
* Excluded view classes use partial matching (contains) - if a view's class name contains the pattern, it's excluded
* Included view classes use exact matching - they remove specific view types from the excluded set

When a view type is ignored from subtree traversal:

* The view itself is still redacted (unless it's explicitly marked to be ignored, like `UISwitch`)
* The SDK skips traversing the view's subtree, preventing crashes from accessing problematic sublayers
* This prevents crashes while maintaining privacy protection for the view itself

## [Debugging Session Replay masking](https://docs.sentry.io/platforms/apple/guides/ios/session-replay/customredact.md#debugging-session-replay-masking)

To see how elements are being masked, enable the masking preview from anywhere in your app. It will display an overlay on top of the masked elements. This works on the simulator and on device, as well as within Xcode Preview.

```swift
SentrySDK.replay.showMaskPreview()
```

By default, the overlay will be opaque. To configure the opacity, pass the desired opacity as a parameter:

```swift
SentrySDK.replay.showMaskPreview(0.5) // 0.5 opacity to render the preview semi-transparent
```

Make sure not accidentally include this in your release build by e.g. wrapping it in a `#if DEBUG` block.

```swift
#if DEBUG
  SentrySDK.replay.showMaskPreview()
#endif
```

To preview masking during the design phase of your SwiftUI views, use the `sentryReplayPreviewMask` modifier.

This view modifier works on the simulator and on device, as well as within Xcode Preview. Therefore we recommend to apply the modifier only in your preview code, to ensure proper masking without affecting the final release build.

Note that when you apply this modifier to a view, it will show the masking preview for the entire window containing that view, not just the view itself.

```swift
struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
      .sentryReplayPreviewMask()
  }
}
```
