# Custom Context in JavaScript SDK


## Overview

Custom context lets you attach enrichment data once and have the JavaScript SDK add it to subsequent events during the current page-load lifetime. Use it for metadata that applies to many events, such as an experiment bucket, account region, application environment, or operator impersonation details.

The SDK adds custom context fields directly under the event's `context` object; it doesn't nest them under a `customContext` key. Custom context is stored in memory only. It doesn't persist across page reloads and doesn't use cookies, `localStorage`, or `sessionStorage`.

{{< warning >}}
Custom context is available in the current JavaScript SDK v3 package, [`@rudderstack/analytics-js`](https://www.npmjs.com/package/@rudderstack/analytics-js). It doesn't apply to deprecated JavaScript SDK packages or other SDKs.
{{< /warning >}}

## Set context at load time

Use the `context` field in the [`load` API options]({{< ref "sources/event-streams/sdks/rudderstack-javascript-sdk/load-js-sdk.md#loading-options" >}}) to seed the initial global custom context:

```javascript
rudderanalytics.load(WRITE_KEY, DATA_PLANE_URL, {
  context: {
    appTier: 'premium',
    region: 'EU'
  }
});
```

The SDK applies this context before it processes buffered event calls after a valid load. This load option seeds the stored global custom context; it's different from the SDK-generated event `context` fields and from per-call `apiOptions` context overrides.

If the `context` value is invalid, the SDK still loads and leaves the stored custom context empty.

## Runtime APIs

The JavaScript SDK exposes the following custom context APIs:

| API | Description |
| :-- | :-- |
| `setCustomContext(context)` | Deep-merges the provided object into the stored custom context. |
| `getCustomContext()` | Returns a snapshot of the current custom context. Mutating the returned object doesn't mutate SDK state. |
| `clearCustomContext()` | Clears all stored custom context. |

```javascript
rudderanalytics.setCustomContext({
  experimentBucket: 'B'
});
rudderanalytics.getCustomContext();
rudderanalytics.clearCustomContext();
```

`setCustomContext` and `clearCustomContext` are synchronous after the SDK is loaded. `getCustomContext()` returns the current stored snapshot.

## Pre-load buffering

Custom context uses the JavaScript SDK's [pre-load API buffering]({{< ref "sources/event-streams/sdks/rudderstack-javascript-sdk/supported-api.md#pre-load-api-buffering" >}}). If you call `setCustomContext` or `clearCustomContext` before a valid `load`, the SDK buffers the call and replays it with the other buffered API calls after load.

The `context` load option seeds the stored custom context first. The SDK then replays buffered custom context updates and event calls in order, so each update affects only the event calls that follow it.

For example:

```javascript
rudderanalytics.load(WRITE_KEY, DATA_PLANE_URL, {
  context: {
    appTier: 'premium'
  }
});

rudderanalytics.track('Before Runtime Update');
rudderanalytics.setCustomContext({
  experimentBucket: 'B'
});
rudderanalytics.track('After Runtime Update');
rudderanalytics.clearCustomContext();
rudderanalytics.track('After Clear');
```

In this sequence:

- `Before Runtime Update` includes `context.appTier`.
- `After Runtime Update` includes `context.appTier` and `context.experimentBucket`.
- `After Clear` doesn't include the custom context fields.

`getCustomContext()` isn't buffered and returns an empty object if you call it before the SDK is loaded.

## Deletion semantics

To remove a field from the stored custom context, set that object property to `null` or explicitly supplied `undefined` in a `setCustomContext` call:

```javascript
rudderanalytics.setCustomContext({
  region: 'EU',
  account: {
    plan: 'pro',
    seats: 5
  }
});

rudderanalytics.setCustomContext({
  region: null,
  account: {
    plan: undefined
  }
});

rudderanalytics.getCustomContext();
// { account: { seats: 5 } }
```

Deletion markers apply to object properties at any nested object level. If you delete the last field in a nested object, the parent object remains as an empty object. A `null` or `undefined` entry inside an array is kept as a value, not treated as a deletion.

To remove an array completely, set the enclosing object property to `null` or `undefined`. Passing an empty array doesn't remove an existing array.

## Merge precedence

The SDK builds the final event `context` using the following precedence, from lowest to highest:

1. SDK built-in context.
2. Global custom context from the load option or `setCustomContext`.
3. Per-call `apiOptions` context for the current event.

Per-call `apiOptions` remain ephemeral. You can pass extra context as additional keys in `apiOptions` or nested under `apiOptions.context`. They can override global custom context for one event, but they don't mutate the stored custom context.

For example:

```javascript
rudderanalytics.setCustomContext({
  account: {
    plan: 'pro',
    region: 'EU'
  }
});

rudderanalytics.track('Order Completed', {
  revenue: 30
}, {
  account: {
    region: 'US'
  }
});

rudderanalytics.getCustomContext();
// { account: { plan: 'pro', region: 'EU' } }
```

In this example, the `Order Completed` event uses `context.account.region` as `US`, but the stored custom context still has `region` as `EU`.

## Reserved keys

The following root context keys are SDK-managed and can't be set through custom context or per-event `apiOptions`:

- `library`
- `consentManagement`
- `userAgent`
- `ua-ch`
- `screen`

If you include any of these keys in load-time context, `setCustomContext`, or per-event `apiOptions`, the SDK drops the key and emits a console warning. The warning identifies the rejected key but doesn't log the rejected value.

Reserved-key filtering applies only at the root of the context object. The same names can appear below another custom key.

Other built-in context fields, such as `app`, `campaign`, `locale`, and `page`, follow the normal merge order and can be extended or overridden.

## Event coverage

Custom context applies to subsequent public event API calls:

- `track`
- `page`
- `identify`
- `group`
- `alias`

Automatic events that use these APIs also receive custom context. SDK diagnostics, error reports, and internal telemetry don't receive custom context.

The SDK merges custom context into the event payload before sending it, so both cloud-mode and device-mode destinations receive the same final `context`.

## Important considerations

- `rudderanalytics.reset()` doesn't clear custom context. Use `clearCustomContext()` explicitly to clear it.
- Arrays merge by index. Right-hand entries replace or recursively merge with entries at the same index, while unmatched left-hand entries are retained. For example, merging `{ tags: ['a', 'b', 'c'] }` with `{ tags: ['x', 'y'] }` produces `{ tags: ['x', 'y', 'c'] }`.
- Invalid input, such as a non-plain object or keys like `__proto__`, `constructor`, or `prototype`, is rejected with a warning and doesn't change the stored context.
- Custom context values are customer-provided event payload data. Avoid adding sensitive data unless your RudderStack configuration and downstream destinations support that use case.

## TypeScript types

The public TypeScript definitions use the following custom context types:

```typescript
type CustomContextValue = string | number | boolean | Date | CustomContext | CustomContextValue[];
type CustomContext = Record<string, CustomContextValue>;
type InputCustomContextValue = string | number | boolean | Date | null | undefined | InputCustomContext | CustomContextValue[];
type InputCustomContext = Record<string, InputCustomContextValue>;
```

Use `InputCustomContext` when setting custom context because `null` and `undefined` are accepted as deletion markers. `getCustomContext()` returns `CustomContext`, which doesn't include deletion markers.

## Use cases

Common use cases include:

| Use case | Example custom context |
| :-- | :-- |
| Impersonation or acting-agent metadata during operator sessions | `{ impersonation: { agentId: 'agent-42' } }` |
| A/B experiment bucketing applied globally | `{ experimentBucket: 'B' }` |
| App version or environment tagging | `{ app: { version: '2.4.0', environment: 'production' } }` |
| Region or account metadata enrichment | `{ region: 'EU', account: { plan: 'pro' } }` |

```javascript
rudderanalytics.setCustomContext({
  impersonation: {
    agentId: 'agent-42'
  }
});

rudderanalytics.track('Account Updated');

rudderanalytics.setCustomContext({
  impersonation: null
});
```

The `Account Updated` event includes `context.impersonation.agentId`. Subsequent events after the final `setCustomContext` call don't include the `impersonation` field.

## See more

- [Load JavaScript SDK]({{< ref "sources/event-streams/sdks/rudderstack-javascript-sdk/load-js-sdk.md" >}})
- [JavaScript SDK APIs]({{< ref "sources/event-streams/sdks/rudderstack-javascript-sdk/supported-api.md" >}})
- [Contextual fields]({{< ref "event-spec/standard-events/common-fields.md#automatically-collected-contextual-fields" >}})

<br />

