> ## Documentation Index
> Fetch the complete documentation index at: https://launchdarkly.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Getting started with observability for .NET MAUI

This guide shows how to add LaunchDarkly observability to a .NET MAUI application targeting Android and iOS. Because MAUI is a single C# codebase that compiles to both platforms, one path serves both targets. Platform-specific differences are called out inline as you reach them. By the end, you'll have the SDK and plugins installed, observability and session replay turned on, and confirmation that data is flowing.

<Note>
  **This SDK is in early access**

  The MAUI observability plugin is in early access. Its APIs may change before the 1.x release.
</Note>

The .NET MAUI SDK supports two complementary telemetry plugins:

* **Observability** captures errors, logs, and traces. Use it to debug failures in production, watch error rates for a release, and trace network calls end-to-end. The SDK does not report crashes automatically. To report an exception, call `LDObserve.RecordError`.
* **Session replay** captures UI snapshots of what a user encountered. Use it when a stack trace alone doesn't tell you why a user got stuck, and pair it with masking to keep sensitive fields out of the recording.

Both features come from a single `ObservabilityPlugin`, which installs alongside the LaunchDarkly .NET MAUI SDK. To learn more about every configuration field, the full `LDObserve` method surface, and advanced distributed-tracing patterns, read the [.NET MAUI SDK observability reference](/docs/sdk/observability/maui).

## Prerequisites

To complete this guide, you need:

* **.NET 9 or .NET 10** with the **MAUI workload** installed.
* A target device or emulator: **Android** API level 23+ (Android 6.0, Marshmallow), or **iOS** 15+.
* A **LaunchDarkly account** and a **mobile key** for the environment you're testing against. Find the mobile key on the [**SDK keys** page under **Settings**](/docs/home/account/environment/keys#view-or-copy-sdk-credentials).

## Step 1: Install the SDK

Add the `LaunchDarkly.SessionReplay` package to your MAUI project:

<CodeGroup>
  ```bash title="Install the SDK" lines wrap theme={null}
  dotnet add package LaunchDarkly.SessionReplay
  ```
</CodeGroup>

Then run a clean build before anything else:

<CodeGroup>
  ```bash title="Clean build" lines wrap theme={null}
  dotnet restore
  dotnet clean
  dotnet build
  ```
</CodeGroup>

<Warning>
  **Don't skip the clean**

  Stale build artifacts cause hard-to-diagnose failures, especially on iOS targets. If you hit dependency-resolution errors, running `dotnet restore` first usually clears them.
</Warning>

<Note>
  **Visual Studio on Windows (Android target)**

  If you build for Android from Visual Studio on Windows, the Android SDK may land in a system-protected directory the build can't write to, causing build errors. The fix is to relocate the Android SDK to a user-writable location. For step-by-step instructions, read the [Android SDK relocation guide for Windows](https://github.com/launchdarkly/observability-sdk/blob/main/sdk/%40launchdarkly/mobile-dotnet/docs/move-android-sdk-windows.md). This only affects Android-on-Windows.
</Note>

To record spans for outgoing `HttpClient` requests, add the `HttpActivityPropagationSupport` property to your app's project file. Without it, the .NET trimmer removes the diagnostics that the SDK listens to, and no HTTP spans are recorded:

<CodeGroup>
  ```xml title="Enable HTTP request tracing" lines wrap theme={null}
  <PropertyGroup>
    <HttpActivityPropagationSupport>true</HttpActivityPropagationSupport>
  </PropertyGroup>
  ```
</CodeGroup>

Import the namespaces where you'll initialize the SDK:

<CodeGroup>
  ```csharp title="Import namespaces" lines wrap theme={null}
  using LaunchDarkly.SessionReplay;
  using LaunchDarkly.Sdk;
  using LaunchDarkly.Sdk.Client;
  using LaunchDarkly.Sdk.Client.Integrations;
  using LaunchDarkly.Observability;
  ```
</CodeGroup>

## Step 2: Initialize the SDK

Initialization belongs in `MauiProgram.cs`, alongside the rest of your `MauiApp.CreateBuilder()` configuration. This example references `LDConfig.MobileKey`. `LDConfig` is a small static class that holds the mobile key. Replace that reference with your own constant or with an inline string literal:

<CodeGroup>
  ```csharp title="MauiProgram.cs" lines wrap theme={null}
  using LaunchDarkly.Observability;
  using LaunchDarkly.Sdk;
  using LaunchDarkly.Sdk.Client;
  using LaunchDarkly.Sdk.Client.Integrations;
  using mood_tracker.Observability;

  namespace mood_tracker;

  public static class MauiProgram
  {
      public static MauiApp CreateMauiApp()
      {
          var builder = MauiApp.CreateBuilder();
          builder.UseMauiApp<App>();

          // … fonts, services, view models, pages …

          var ldConfig = Configuration.Builder(
                  LDConfig.MobileKey,
                  ConfigurationBuilder.AutoEnvAttributes.Enabled)
              .Plugins(new PluginConfigurationBuilder()
                  .Add(new ObservabilityPlugin(new ObservabilityOptions(
                      isEnabled: true,
                      serviceName: "maui")))
              ).Build();

          var context  = Context.New("demo-user");
          LdClient.Init(ldConfig, context, TimeSpan.FromSeconds(10));

          return builder.Build();
      }
  }
  ```
</CodeGroup>

The `serviceName` is how this app appears in the LaunchDarkly UI, so make it recognizable.

## Step 3: Turn on session replay

Pass a `SessionReplayOptions` object as the second argument to `ObservabilityPlugin`:

<CodeGroup>
  ```csharp title="Turn on session replay" lines wrap theme={null}
  var ldConfig = Configuration.Builder(
          LDConfig.MobileKey,
          ConfigurationBuilder.AutoEnvAttributes.Enabled)
      .Plugins(new PluginConfigurationBuilder()
          .Add(new ObservabilityPlugin(
              new ObservabilityOptions(
                  isEnabled: true,
                  serviceName: "maui"),
              new SessionReplayOptions(
                  isEnabled: true)))
      ).Build();
  ```
</CodeGroup>

## Step 4: Decide what to mask

Masking is configured through `PrivacyOptions` on `SessionReplayOptions`. There are several configuration options depending on your use case.

They are:

* **Production**: Mask everything, including labels, images, and web views.
* **Local debugging**: Turn masking off on your own test data so you can see what's going on.
* **Balanced**: Mask inputs and web views (passwords, payment forms, and similar) but keep ordinary labels and images visible.

<CodeGroup>
  ```csharp title="Production" lines wrap theme={null}
  var privacy = new SessionReplayOptions.PrivacyOptions(
      maskTextInputs: true,
      maskWebViews: true,
      maskLabels: true,
      maskImages: true
  );
  ```

  ```csharp title="Local debugging" lines wrap theme={null}
  var privacy = new SessionReplayOptions.PrivacyOptions(
      maskTextInputs: false,
      maskWebViews: false,
      maskLabels: false,
      maskImages: false
  );
  ```

  ```csharp title="Balanced" lines wrap theme={null}
  var privacy = new SessionReplayOptions.PrivacyOptions(
      maskTextInputs: true,
      maskWebViews: true,
      maskLabels: false,
      maskImages: false
  );
  ```
</CodeGroup>

The simple rule is to **start fully masked, and only unmask what you've confirmed is safe to record.**

<Frame caption="A session replay with masking applied: the password field is captured as a blank block while non-sensitive labels remain readable.">
  <img src="https://mintcdn.com/launchdarkly/hutarVphEq2dY_zb/images/auto/guide-masking-example-maui-app-SR.png?fit=max&auto=format&n=hutarVphEq2dY_zb&q=85&s=d9253a9b9e4c2d8d00509ca1202a7fd7" alt="A MAUI session replay showing a login screen where the password entry is rendered as a solid block while surrounding labels remain visible, demonstrating text-input masking." width="1914" height="1039" data-path="images/auto/guide-masking-example-maui-app-SR.png" />
</Frame>

### Mask a specific element

When a preset is almost right but one field is the exception, override it at the element level. Call `.LDMask()` on the underlying view after you have a reference to it, or call `.LDUnmask()` to reveal something the preset would hide:

<CodeGroup>
  ```csharp title="Per-view masking" lines wrap theme={null}
  var passwordEntry = new Entry { Placeholder = "Password", IsPassword = true };
  passwordEntry.LDMask();

  var publicInfoLabel = new Label { Text = "Public information" };
  publicInfoLabel.LDUnmask();
  ```
</CodeGroup>

<Warning>
  **Timing matters**

  `LDMask()` and `LDUnmask()` reach the underlying view, so the view's handler must be attached first. Call them after the view is in the visual tree, for example in a page's `Appearing` event, not in a constructor.
</Warning>

## Step 5: Run it and confirm data is arriving

Build and run on a device or emulator:

<CodeGroup>
  ```bash title="Build and run" lines wrap theme={null}
  dotnet build -t:Run -f net9.0-android   # or net10.0-android, or your iOS target framework
  ```
</CodeGroup>

Test out the app by navigating between a couple of screens. Trigger an error deliberately if you want to verify that Observability captures it correctly.

Then open the [Observability](/docs/home/observability) page in the LaunchDarkly UI. Logs and errors attributed to your `serviceName` appear there, as well as a session replay recording you can scrub through with your masking choices applied. When your service name appears in Observability, that is a confirmation that the SDK initialized successfully and is recording data.

<Frame caption="Logs and errors arriving in LaunchDarkly, attributed to the `serviceName` you set in Step 2.">
  <img src="https://mintcdn.com/launchdarkly/hutarVphEq2dY_zb/images/auto/guide-maui-app-observability-logs.png?fit=max&auto=format&n=hutarVphEq2dY_zb&q=85&s=065cdf1f984bb3f34a8e9b048bd87336" alt="The LaunchDarkly observability view listing logs and errors from a MAUI app, attributed to the service name set during initialization." width="1911" height="1039" data-path="images/auto/guide-maui-app-observability-logs.png" />
</Frame>

<Frame caption="A recorded session in the replay player. You can scrub the timeline to watch what the user did leading up to an error, with your masking choices applied throughout.">
  <img src="https://mintcdn.com/launchdarkly/hutarVphEq2dY_zb/images/auto/guide-session-replay-recording-maui-app.png?fit=max&auto=format&n=hutarVphEq2dY_zb&q=85&s=4ea9cf670a514e7705a6ed894b097b63" alt="The LaunchDarkly session replay player showing a recorded MAUI session with a scrub bar and timeline." width="1920" height="1241" data-path="images/auto/guide-session-replay-recording-maui-app.png" />
</Frame>

After data shows up on one target, the other target reports the same way after you build and run it there.

## Complete code sample

Here is a `MauiProgram.cs` showing observability and session replay wired into MAUI's startup. `LDConfig` is a small static class that holds the `MobileKey` constant. The privacy block uses the SDK default (mask text inputs only). Swap in any preset from Step 4 to change what session replay records:

<CodeGroup>
  ```csharp title="MauiProgram.cs (complete)" lines wrap theme={null}
  using LaunchDarkly.Observability;
  using LaunchDarkly.Sdk;
  using LaunchDarkly.Sdk.Client;
  using LaunchDarkly.Sdk.Client.Integrations;
  using LaunchDarkly.SessionReplay;
  using mood_tracker.Observability;

  namespace mood_tracker;

  public static class MauiProgram
  {
      public static MauiApp CreateMauiApp()
      {
          var builder = MauiApp.CreateBuilder();
          builder.UseMauiApp<App>();

          // … fonts and other MAUI configuration …

          var ldConfig = Configuration.Builder(
                  LDConfig.MobileKey,
                  ConfigurationBuilder.AutoEnvAttributes.Enabled)
              .Plugins(new PluginConfigurationBuilder()
                  .Add(new ObservabilityPlugin(
                      new ObservabilityOptions(
                          isEnabled: true,
                          serviceName: "maui"),
                      new SessionReplayOptions(
                          isEnabled: true,
                          privacy: new SessionReplayOptions.PrivacyOptions(
                              maskTextInputs: true,
                              maskWebViews: false,
                              maskLabels: false,
                              maskImages: false))))
              ).Build();

          var context  = Context.New("demo-user");
          LdClient.Init(ldConfig, context, TimeSpan.FromSeconds(10));

          // … register your own services, view models, and pages …

          return builder.Build();
      }
  }
  ```
</CodeGroup>

## What to explore next

* **Every configuration field**: The complete `ObservabilityOptions` and `SessionReplayOptions` tables are in the [SDK reference documentation](/docs/sdk/observability/maui) and in [Configuration for client-side observability](/docs/sdk/features/observability-config-client-side).
* **Custom tracing**: Time your own operations with `LDObserve` spans. The reference's distributed-tracing section covers root spans, nesting, cross-thread context, and automatic `HttpClient` instrumentation.
* **How this relates to OpenTelemetry**: The plugin is an OpenTelemetry implementation under the hood. If you already use `System.Diagnostics.Activity`, `LDObserve` has `StartActivity`, `StartRootActivity`, and `GetActivitySource` equivalents so your existing trace code flows in.
* **Working with your data**: After telemetry is flowing, set up alerts and dashboards, then investigate issues with [Vega](/docs/home/getting-started/vega).

## Troubleshooting

Use this section to investigate common issues.

### No data appearing

If no data appears in LaunchDarkly:

* Confirm you pasted the **mobile key** for the environment you're viewing in the UI, and not for a different environment.
* Make sure plugin registration runs in `MauiProgram.cs` and that the configured builder is actually returned from `CreateMauiApp()`.
* Test the app a bit more and refresh. Data can take a few minutes to appear.

### Session replay isn't recording, but observability works

If observability data arrives but session replays don't:

* Confirm that you pass a `SessionReplayOptions` object as the second argument to `ObservabilityPlugin`. Without it, the plugin records observability data only.
* If you set `isEnabled` to `false` on `SessionReplayOptions` for consent-gated rollout, confirm the call that enables it is actually running.

### HTTP requests don't appear in traces

Confirm that `HttpActivityPropagationSupport` is set to `true` in your app's project file. The .NET Android SDK turns it off by default, and the .NET iOS SDK turns it off in Release builds. To learn how to set it, read [Step 1: Install the SDK](#step-1-install-the-sdk).

### Build failures

If your app fails to build:

* Run `dotnet clean` and rebuild, especially on iOS.
* On Windows building for Android, check the Android SDK relocation note in [Step 1: Install the SDK](#step-1-install-the-sdk).

### Masking not applied to a specific view

Confirm that your app calls `.LDMask()` or `.LDUnmask()` after the view's handler is attached, for example in `Appearing`, not in a constructor.

## Conclusion

In this guide, you instrumented a .NET MAUI application for LaunchDarkly observability. You can now:

* Capture errors, logs, and session replays from both Android and iOS with one C# codebase
* Control what session replay records through `PrivacyOptions` presets and per-view masking
* Confirm telemetry is flowing in the LaunchDarkly UI, ready for alerts, dashboards, and Vega

<View title="Developer">
  <Note>
    **Want to know more? Start a trial.**

    Your 14-day trial begins as soon as you sign up. Get started in minutes using the in-app Quickstart. You'll discover how easy it is to release, monitor, and optimize your software.<br /><br />

    Want to try it out? <a href="https://app.launchdarkly.com/signup">Start a trial</a>.
  </Note>
</View>

<View title="Federal docs" />

<View title="EU docs" />
