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

# Privacy and filtering for browser data

<Note>
  Browser monitoring is a beta [AppSignal Labs](/labs) feature. The
  `@appsignal/browser` package is published under the `beta` tag on npm, and its
  configuration and API may still change between beta releases. Share feedback
  in our [Discord community](https://discord.gg/EjF6ykYx63).
</Note>

Front-end telemetry sits closer to personal data than back-end telemetry does. URLs carry tokens, forms carry card numbers, and the text on a button can be an order total. AppSignal for Browser is conservative by default, and every remaining decision is yours to configure.

## What is protected by default

Without any configuration:

* **Every query parameter is removed** from every URL AppSignal records. `?token=abc` never leaves the browser.
* **Request and response bodies are never recorded.** There is no option to turn that on.
* **No cookies are read or written.** The SDK stores a few `appsignal_*` values in `localStorage` and `sessionStorage`: random IDs for the session, the tab, and the visitor, the time of the last activity, and any tags you set. The visitor ID is a random value with nothing personal in it. It does stay on the device between visits, though, so treat it as an identifier and mention it in your privacy notice.
* **Console output is cut off** after the first 200 characters.
* **Nothing is sent when `active` is `false`.** The SDK does not change anything on the page, start any timers, or make any requests.

## URLs and query parameters

`privacy.queryParamsAllowlist` lists the query parameters worth keeping. Everything else is removed. You can use `*` to match several names at once, so `utm_*` keeps every UTM parameter. The default list is empty, which removes all of them.

This applies to every URL AppSignal records: the URLs in network and navigation breadcrumbs, the page an error happened on, the page the user came from, and the page a web vital belongs to.

<CodeGroup>
  ```js JavaScript theme={null}
  init({
    key: "<YOUR_FRONTEND_API_KEY>",
    privacy: {
      queryParamsAllowlist: ["utm_*", "page"],
    },
  });
  ```
</CodeGroup>

With that setting:

| Captured URL                                                      | Reported as                                      |
| ----------------------------------------------------------------- | ------------------------------------------------ |
| `https://example.com/search?q=alice@corp.com&page=2`              | `https://example.com/search?page=2`              |
| `https://example.com/dashboard?utm_source=email&token=abc`        | `https://example.com/dashboard?utm_source=email` |
| `https://example.com/callback#access_token=ey…&token_type=bearer` | `https://example.com/callback`                   |
| `https://example.com/checkout#/payment`                           | unchanged                                        |

The last two rows are the important pair. Both URLs have something after a `#`, and they are treated differently on purpose.

The third one looks like a query string after the `#`, so the same rules apply and the login token is removed. This is the case that matters: a token like that should never reach AppSignal. The fourth one looks like a route, which is how many single-page applications do their routing, so it is kept exactly as it is. A plain link to a section of a page, such as `#pricing`, is kept too.

## Excluding network requests

`privacy.networkBlocklist` lists requests that should leave no breadcrumb. The request still happens as normal. It is only the record of it that is skipped.

Patterns are matched against the host and the path of the URL. A single `*` matches one part of the path, and `**` matches any number of parts.

<CodeGroup>
  ```js JavaScript theme={null}
  init({
    key: "<YOUR_FRONTEND_API_KEY>",
    privacy: {
      networkBlocklist: [
        "api.stripe.com/**",      // every request to that host
        "*/auth/token",           // that path on any host
        "example.com/users/*/card" // any single ID in that position
      ],
    },
  });
  ```
</CodeGroup>

## Masking and blocking page elements

Two options control what gets recorded when a user selects something. Both take CSS selectors, and both apply to everything inside the element you name, so listing a container covers all of its contents.

`privacy.dom.maskText` replaces the element's text with `[masked]`. The breadcrumb is still recorded, so you keep the fact that someone selected it:

```
maskText: [".order-total"]

click  button "Pay $42.00"   →   click  button "[masked]"
```

`privacy.dom.blockElement` records nothing at all. Use it for card forms, identity numbers, and anything that should never surface:

```
blockElement: ["#card-form"]

click  input#cvc   →   (nothing recorded)
```

<Warning>
  These two options only affect what is recorded when a user selects something.
  They do not touch error messages, network URLs, or console output. Use
  `queryParamsAllowlist`, `networkBlocklist`, and the two functions in the
  following section for those.
</Warning>

## Filtering errors and breadcrumbs

Two functions let you inspect each error and each breadcrumb before AppSignal stores or sends anything. Neither can be `async`.

`beforeError` runs once for every error. Return `null` to throw the error away, or change its fields to remove sensitive text from them. It is the one place to do both:

<CodeGroup>
  ```js JavaScript theme={null}
  init({
    key: "<YOUR_FRONTEND_API_KEY>",
    beforeError: (event) => {
      // Drop known noise
      if (/ResizeObserver/.test(event.message)) return null;

      // Redact email addresses from the message and the stack
      event.message = event.message.replace(EMAIL_PATTERN, "[redacted]");
      event.stack = event.stack?.replace(EMAIL_PATTERN, "[redacted]");
      return event;
    },
  });
  ```
</CodeGroup>

Returning `null` gets rid of the error completely. No breadcrumb is recorded for it, and it does not count towards the [rate limits](/browser/error-tracking#rate-limits).

`beforeBreadcrumb` runs once for every breadcrumb, as it is recorded. Return `null` to throw it away, or change its `message` and `data`:

<CodeGroup>
  ```js JavaScript theme={null}
  init({
    key: "<YOUR_FRONTEND_API_KEY>",
    beforeBreadcrumb: (breadcrumb) => {
      if (breadcrumb.category === "console") return null;
      return breadcrumb;
    },
  });
  ```
</CodeGroup>

`beforeError` cannot see breadcrumbs, because they are attached to the error after this function has approved it. Use `beforeBreadcrumb` to clean up breadcrumb content instead.

<Warning>
  Neither function can be `async`. If `beforeError` returns a promise, AppSignal
  logs a `console.error` and throws the error away. Do any slow work before you
  call `captureError`, not inside these functions.
</Warning>

## Tracking consent

Nothing is collected until `init()` runs, so asking for consent first is a matter of not calling it until you have it:

<CodeGroup>
  ```js JavaScript theme={null}
  import { init, destroy } from "@appsignal/browser";

  if (consent.granted("analytics")) {
    init({ key: "<YOUR_FRONTEND_API_KEY>", endpoint: "https://appsignal-endpoint.net" });
  }

  consent.on("revoked", () => destroy());
  ```
</CodeGroup>

`destroy()` stops collection straight away, sending whatever it had already gathered. It only affects the page the user is on. Store their decision yourself and check it before calling `init()`, or the next page they open starts collecting again.

## What you can tell your users

Here is what AppSignal for Browser records about a visit:

* The pages they opened, and how far down each one they scrolled.
* What they selected, and where on the screen they selected it. Elements are recorded by their label, not their contents, unless the label happens to be the contents.
* The requests the page made, and whether each one succeeded.
* Warnings and errors the page logged to the console.
* Timing measurements.
* On errors, whatever tags you set.

The [breadcrumbs page](/browser/breadcrumbs#what-is-collected) has the full list.

Every batch of web vitals also carries some context about the visit: random IDs for the session, the tab, and the visitor, the page URL, the page they came from, the browser's user agent, the size of their screen and window, their language, and their time zone. Where the browser makes them available, it also carries the connection type and how much memory the device has.

It does not record anything typed into a form, the contents of any request or response, or cookies. The visitor ID is the only identifier the SDK makes up: a random value that means nothing on its own, stored on the device between visits. Anything that identifies a person by name, email, or account is there because your application sent it, through `setTags()` or `addBreadcrumb()`.
