> For the complete documentation index, see [llms.txt](https://docs.groundcover.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.groundcover.com/getting-started/installation-and-updating/connect-rum.md).

# Connect RUM

{% hint style="info" %}
This capability is only available to BYOC deployments. Check out our [pricing page](https://www.groundcover.com/pricing) for more information about subscription plans and the available deployment modes.
{% endhint %}

groundcover’s Real User Monitoring (RUM) SDK captures front-end **performance**, **user interactions**, **errors**, **logs**, **distributed traces**, and **session replay** from your web application — with privacy masking **on by default**.

**Start capturing RUM data** by installing the [browser SDK](https://www.npmjs.com/package/@groundcover/browser) in your web app.

This guide walks you through installing and initializing the SDK, the full configuration reference, identifying users, sending custom events and logs, capturing exceptions, session management, source maps, and session replay.

### Install the SDK

```bash
npm install @groundcover/browser
# or
yarn add @groundcover/browser
```

### Initialize the SDK

A single `init()` call installs every instrumentation (page loads, DOM interactions, network requests, errors, console logs, navigation, and performance) and starts sending data. Session replay is the one exception — it must be started explicitly with [`startReplayRecording()`](#session-replay).

```typescript
import groundcover from '@groundcover/browser';

groundcover.init({
  apiKey: 'your-ingestion-key',
  dsn: 'your-dsn',
  cluster: 'your-cluster',
  appId: 'your-app-id',
  environment: 'production',
});
```

From here you can enrich it:

```typescript
// Tie events to a user
groundcover.identifyUser({ id: 'u_123', email: 'john@acme.com', organization: 'acme' });

// Capture a handled error
groundcover.captureException(new Error('Checkout failed'), { feature: 'checkout' });

// Emit a structured log
groundcover.logger.warn('Payment retry', { provider: 'stripe', attempt: 2 });

// Emit a custom business event
groundcover.sendCustomEvent({ event: 'plan_upgraded', attributes: { plan: 'pro' } });
```

## Configuration

`init()` takes your connection and identity fields at the top level, plus an `options` object for behavioral configuration.

### Connection & identity

| Field                                         | Required | Description                                                                                                                                                                                                                                      |
| --------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `apiKey`                                      | ✅        | A dedicated Ingestion Key of type `RUM` (Settings → Access → Ingestion Keys).                                                                                                                                                                    |
| `dsn`                                         | ✅        | Your public groundcover endpoint, in the format `https://example.platform.grcv.io`, where `example.platform.grcv.io` is your `ingress.site` installation value.                                                                                  |
| `cluster`                                     | ✅        | Identifier for your cluster; helps filter RUM data by cluster.                                                                                                                                                                                   |
| `appId`                                       | ✅        | Application identifier; reported as `service.name`.                                                                                                                                                                                              |
| `environment`                                 | —        | Deployment environment (e.g. `production`, `staging`) used for filtering.                                                                                                                                                                        |
| `namespace`, `releaseId`, `user`, `sessionId` | —        | Optional identity/grouping fields. `releaseId` associates uploaded [source maps](#source-maps) with a release; `user` matches [`identifyUser`](#identify-users); `sessionId` enables [shared sessions](#micro-frontend-session-synchronization). |

### Behavioral options

All behavioral configuration lives under `options`, grouped by concern — sampling (`sessionSampleRate`, `eventSampleRate`), enabled instrumentations (`enabledEvents`), `excludedUrls`, the `beforeSend` / `enrichEvent` hooks, and the `privacy`, `tracing`, `transport`, and `replay` groups. You can update it at runtime with `groundcover.updateConfig(...)`.

For the complete, always-current configuration reference — every option, type, and default — see the [`@groundcover/browser` package on npm](https://www.npmjs.com/package/@groundcover/browser). Data masking is **on by default**; see [Privacy and data masking](#privacy-and-data-masking) below.

## Privacy and data masking

Masking is **on by default** (`privacy.level: 'mask-sensitive'`). A single `level` is the master switch; finer toggles and hooks refine it.

| `level`                        | Replay inputs    | Replay text                               | DOM events       | Network / logs / errors               |
| ------------------------------ | ---------------- | ----------------------------------------- | ---------------- | ------------------------------------- |
| `mask-sensitive` **(default)** | sensitive masked | `[data-private]` + `maskSelectors` masked | sensitive masked | redacted                              |
| `mask-all`                     | masked           | masked (`*`)                              | all masked       | redacted                              |
| `allow`                        | —                | —                                         | —                | off (auth headers are still stripped) |

Under `mask-sensitive`, an input/element is masked when it is a `type="password"`, sits under a `[data-private]` ancestor or a `maskSelectors` match, or has an `id`/`name`/`class`/`aria-label`/`placeholder` matching a built-in sensitive-key pattern or your `sensitiveKeys`. Non-sensitive inputs and static page text stay visible; use `[data-private]` / `maskSelectors` to mask static content.

```typescript
options: {
  privacy: {
    level: 'mask-sensitive',
    maskSelectors: ['.pii', '#ssn'],
    sensitiveKeys: ['account_no'],
  },
}
```

**Built-in sensitive patterns** (always treated as sensitive, case-insensitive; your `sensitiveKeys` merge on top):

* **Key substrings** — matched inside a body/query key or a DOM element attribute (`id`/`name`/`class`/`aria-label`/`placeholder`): `token`, `secret`, `passwd`, `password`, `api_key`, `access_key`, `write_key`, `auth`, `bearer`, `credential`, `cvv`, `ssn`, `credit_card`, `card_number` (the `_` in the last five is optional — `apikey` / `api-key` also match).
* **Request/response headers** — always stripped regardless of `level`: `authorization`, `cookie`, `set-cookie`, plus any header name containing `token`, `key`, `secret`, `passwd`, `password`, `auth`, `bearer`, or `credential`.
* **Query / form param names** — matched as a whole key (not inside JSON bodies), for OAuth-style callbacks: `code`, `state`, `session_state`, `id_token`, `access_token`, `refresh_token`, `token`.

To turn masking off, set `privacy: { level: 'allow' }`. Note this disables masking only — auth/request headers are **still stripped** regardless of `level` (see the header list above).

## Identify Users

Link RUM data to specific users. An omitted key leaves the current identity untouched; pass `null` via `updateConfig` to clear it (e.g. on logout).

```typescript
groundcover.identifyUser({
  id: 'u_123',
  email: 'john@acme.com',
  name: 'John Doe',
  role: 'admin',
  organization: 'acme',
  properties: { plan: 'pro' },
});
```

## Send Custom Events

Instrument key user interactions or business events:

```typescript
groundcover.sendCustomEvent({
  event: 'PurchaseCompleted',
  attributes: { orderId: 1234, amount: 99.99 },
});
```

{% hint style="info" %}
Custom event payloads are **not** auto-redacted (they’re deliberately provided). Scrub sensitive fields yourself, or via `enrichEvent`.
{% endhint %}

## Capture Exceptions

Manually track caught errors with optional context:

```typescript
try {
  performAction();
} catch (error) {
  groundcover.captureException(error, { userId: '123', feature: 'checkout' });
}
```

## Send Logs

`groundcover.logger` provides one method per level — `log`, `info`, `warn`, `error`, `debug`, `trace`. The second argument is an attributes object; nested objects are flattened to dotted keys.

```typescript
groundcover.logger.info('User entered new experience', { releaseId: '1.5.3' });

groundcover.logger.warn('Checkout failed', {
  orderId: 'ord_42',
  cart: { items: 3, total: 99.99 }, // → cart.items, cart.total
});
```

The SDK also auto-captures `console.*` calls; when any argument is a plain object, its keys are promoted to structured log attributes. Reserved keys (`message`, `level`, `location`) are always set by the SDK and can’t be overridden.

## Session Management

Read or override the current session id:

```typescript
const id = groundcover.getSessionId();

groundcover.setSessionId('my-session-id'); // omit the argument to mint a fresh id
```

### Micro-frontend session synchronization

Pass a shared `sessionId` so multiple frontends report under one session:

```typescript
const sharedSessionId = 'session-12345';
groundcover.init({ /* …app… */ apiKey, dsn, cluster, appId: 'shell', sessionId: sharedSessionId });
groundcover.init({ /* …mfe… */ apiKey, dsn, cluster, appId: 'micro-frontend', sessionId: sharedSessionId });
```

## Session lifecycle

`sessionMaxDuration` sets a **target** maximum wall-clock session length (default 4 hours; must be between 1 minute and 8 hours).

It is enforced **lazily, on activity — not by a background timer**, so it is **not a hard upper bound**. Once the cap has elapsed, the *next* user/business event (click, navigation, log, custom, network, exception, …) flushes pending events under the current session id, mints a fresh id, and resumes replay recording if it had been active.

Because rotation is activity-gated, a session that goes idle keeps its id past the cap until the next qualifying event. Sessions are also bounded by a 30-minute inactivity gap, enforced the same lazy way. The flush is best-effort and the rotation always proceeds regardless of delivery success. Invalid values fall back to the default with a `console.warn`.

## Manual navigation

When `navigation` isn’t auto-tracked (e.g. a custom router), you can bracket navigation spans manually:

```typescript
groundcover.startNavigation({ to: '/checkout' });
// …route transition…
groundcover.endNavigation({ to: '/checkout' });
```

## Source Maps

Source maps map minified/bundled code back to your original source files. With them, stack traces in RUM (e.g. in session details and exceptions) show your real file names, line numbers, and function names instead of bundle names and minified positions.

Pre-condition: source maps were enabled in your CI operation.

To enable source maps in your account, upload them from your CI job via the API:

```
POST /api/rum/sourcemaps
Content-Type: multipart/form-data
```

Required form fields:

* `app_id` - your application identifier (alphanumeric, dots, hyphens, underscores), as provided in the RUM init call.
* `release_id` - the release/version being deployed (same character restrictions), as provided in the RUM init call.
* `file` - the source map file.

Required headers:

* `Authorization` - groundcover api key - [here is how to generate one](/use-groundcover/remote-access-and-apis/api-keys.md#creation-and-storage).
* `X-Backend-Id` - the relevant BYOC backend, displayed in the api keys page.

Here is an example of source maps uploading using curl:

```shellscript
curl -X POST "https://app.groundcover.com/api/rum/sourcemaps" \
-H "Authorization: Bearer <API_KEY>" \
-H "X-Backend-Id: $BACKEND_ID" \
-F "app_id=my-web-app" \
-F "release_id=1.2.3" \
-F "file=@./dist/main.js.map"
```

Response (201):

```json
{
"status": "uploaded",
"app_id": "my-web-app",
"release_id": "1.2.3",
"filename": "main.js.map",
"size_bytes": 524288
}
```

Files will be stored in the selected provider at the path: `sourcemaps/<app_id>/<release_id>/<filename>`.

Every new RUM trace that holds a stack trace will be automatically converted based on the source map.

## Session Replay

Session replay records your users’ behavior with [rrweb](https://github.com/rrweb-io/rrweb) so you can replay the actions that led to specific RUM events.

Replay recording does **not** start automatically — even when `replay` is in `enabledEvents`. You must start it explicitly (for example, after obtaining user consent), and can stop it before a sensitive section of your app:

```typescript
groundcover.startReplayRecording();
groundcover.stopReplayRecording();
```

Recording is stored on your BYOC server and is deleted along with the RUM session, in accordance with your retention policy.

### Masking replay content

Replay masking is driven by your [`privacy`](#privacy-and-data-masking) config — masking is **on by default**. To mask specific static content, add the `data-private` attribute or a `maskSelectors` match:

```typescript
options: {
  privacy: {
    level: 'mask-sensitive',      // default
    maskSelectors: ['.pii'],       // always masked in replay + DOM
  },
  replay: {
    blockedSelectors: ['.grammarly-extension'], // excluded from recording (noise reduction)
  },
}
```

{% hint style="info" %}
rrweb mask options are fixed at `record()` time. Changing privacy config at runtime via `updateConfig` automatically **restarts** the active recording so the new masking applies.
{% endhint %}

### Viewing sessions

In the [summary page](https://app.groundcover.com/rum/summary), you will see an indication next to sessions with a recording.

<figure><img src="/files/CenWUozMBBzJdwjpHzfx" alt=""><figcaption></figcaption></figure>

Within the drawer, open the Session Replay tab to see the recording.

<figure><img src="/files/pFH17muGKYIETHdw00zv" alt=""><figcaption></figcaption></figure>

## API reference

All methods are available on the default export and on `window.groundcover`.

| Method                                                           | Description                                                     |
| ---------------------------------------------------------------- | --------------------------------------------------------------- |
| `init(config)`                                                   | Initialize the SDK and install instrumentation.                 |
| `identifyUser(user)`                                             | Attach user identity to subsequent events.                      |
| `sendCustomEvent({ event, attributes })`                         | Emit a custom business event.                                   |
| `captureException(error, metadata?)`                             | Capture a handled error with optional context.                  |
| `logger.{log,info,warn,error,debug,trace}(message, attributes?)` | Structured logging.                                             |
| `updateConfig({ options?, user?, … })`                           | Update config at runtime (merges nested groups one level deep). |
| `startNavigation(metadata)` / `endNavigation(metadata)`          | Manual navigation spans (when `navigation` isn’t auto-tracked). |
| `getSessionId()` / `setSessionId(id?)`                           | Read / override the current session id.                         |
| `startReplayRecording()` / `stopReplayRecording()`               | Manually control session replay.                                |

## Migrating to 1.0.0

`1.0.0` restructures `options` by concern (a clean break from `0.x`) and removes the deprecated masking flags. Update your config as follows:

| `0.x`                                        | `1.0.0`                                                               |
| -------------------------------------------- | --------------------------------------------------------------------- |
| `environment` *(duplicated in `options`)*    | top-level `environment` only                                          |
| `userIdentifier`                             | `user`                                                                |
| `options.sessionReplay.blockedSelectors`     | `options.replay.blockedSelectors`                                     |
| `options.tracePropagationUrls`               | `options.tracing.propagationUrls`                                     |
| `options.tracePropagationHeaders`            | `options.tracing.propagationHeaders`                                  |
| `options.tracePropagationTraceIdHeaderName`  | `options.tracing.traceIdHeaderName`                                   |
| `options.tracePropagationSpanIdHeaderName`   | `options.tracing.spanIdHeaderName`                                    |
| `options.traceOrigin`                        | `options.tracing.origin`                                              |
| `options.batchSize` / `options.batchTimeout` | `options.transport.batchSize` / `options.transport.batchTimeout`      |
| `options.enableCompression`                  | `options.transport.compression`                                       |
| `options.enableMasking: true` *(removed)*    | set `options.privacy.level: 'mask-all'`                               |
| `options.enableMasking: false` *(removed)*   | set `options.privacy.level: 'allow'`                                  |
| `options.maskFields` *(removed)*             | set `options.privacy.maskSelectors` / `options.privacy.sensitiveKeys` |

{% hint style="warning" %}
The removed masking flags are **ignored, not auto-mapped** — passing `enableMasking` / `maskFields` logs a `console.warn` and has **no effect**. You must set the corresponding `privacy` option yourself. Masking remains **on by default** (`mask-sensitive`); if you previously relied on masking being off, set `privacy: { level: 'allow' }` explicitly.
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.groundcover.com/getting-started/installation-and-updating/connect-rum.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
