---
title: Browser SDK 2 usage guide
description: "Track events, manage identity and sessions, configure Autocapture, and control data delivery with Amplitude Browser SDK 2."
product: analytics
lang: en
last_updated: 2026-10-28
token_estimate: 12954
---
# Browser SDK 2 usage guide

> For AI agents: a documentation index is available at [/docs/llms.txt](/docs/llms.txt). Append `.md` to any page URL for markdown, or send `Accept: text/markdown`.

This guide covers the everyday work with Browser SDK 2 after [Get started](https://amplitude.com/docs/sdks/analytics/browser/browser-sdk-2/get-started): tracking your own events, managing identity and sessions, choosing Autocapture features, and controlling how events reach Amplitude. For exact signatures and every option, go to the [Reference](https://amplitude.com/docs/sdks/analytics/browser/browser-sdk-2/reference). To find the release that added or changed a feature, go to [Browser SDK 2 release notes](https://amplitude.com/docs/sdks/analytics/browser/browser-sdk-2-release-notes).

## Track events

Browser SDK 2 sends five kinds of data: custom events, user properties, group membership, group properties, and revenue. Each has its own method, and each method returns `{ promise }`, which resolves after the upload attempt. Use these methods for events your tracking plan defines. For page views, clicks, form interactions, and other events the SDK can collect without code, go to [Autocapture](#autocapture).

### Track a custom event

Pass an event name and, optionally, an object of event properties.

```ts
amplitude.track("Song Played", {
  title: "Here Comes the Sun",
  duration_seconds: 185,
  genres: ["rock", "pop"],
});
```

Use strings, numbers, booleans, arrays, or nested objects as property values. The event name must be a non-empty string. The SDK drops events with an empty name and logs `Event skipped due to empty event type`.

`logEvent()` is an alias of `track()` with the same arguments.

#### Pass the event as an object

To set more fields than the name and properties, pass a [`BaseEvent`](https://amplitude.github.io/Amplitude-TypeScript/interfaces/_amplitude_analytics_browser.Types.BaseEvent.html) object. This form accepts every field the HTTP V2 API accepts, such as `groups`, `time`, `app_version`, and `insert_id`.

```ts
amplitude.track({
  event_type: "Song Played",
  event_properties: { title: "Here Comes the Sun" },
  time: Date.now() - 60_000,
  insert_id: "song-played-8f2c",
});
```

Set `insert_id` when the same event might send twice, for example after a retry from your own code. Amplitude ignores a second event with the same `insert_id` and `device_id` within seven days.

#### Override identity for one event

Pass `user_id` or `device_id` in the third argument, the event options. The override applies to this event only. Later events still use the identity from `setUserId()` and `setDeviceId()`.

```ts
amplitude.track(
  "Invite Sent",
  { channel: "email" },
  {
    user_id: "user-12345",
  },
);
```

`identify()` behaves differently. Passing `user_id` or `device_id` to `identify()` also calls `setUserId()` or `setDeviceId()`, which changes the identity for every later event.

#### Wait for an event before navigating

The SDK uploads events in batches about once a second. If your code leaves the page right after `track()`, wait for the promise first:

```ts
await amplitude.track("Checkout Started").promise;
window.location.assign("/checkout");
```

The default `fetch` transport already sends with `keepalive`, so most events survive navigation without waiting. Wait when the event must arrive before the next page loads. For more on unload behavior, go to [Data delivery](#send-events-when-the-page-unloads).

### Set user properties

User properties describe the user and stay attached to every later event until they change. Build an `Identify` object, add one or more operations, and pass it to `identify()`.

```ts
const identifyEvent = new amplitude.Identify()
  .set("plan", "pro")
  .setOnce("first_seen_at", "2026-10-26")
  .add("login_count", 1)
  .append("visited_sections", "billing");

amplitude.identify(identifyEvent);
```

The SDK sends an `$identify` event. It doesn't appear as an event in charts, but it updates the user's properties.

| Method | Effect on the user property |
| --- | --- |
| `set(key, value)` | Sets the value, replacing any existing value. |
| `setOnce(key, value)` | Sets the value only if the property has no value yet. Use it for first-touch facts, such as a signup date. |
| `add(key, number)` | Adds the number to the current numeric value. Pass a negative number to subtract. A missing property starts at `0`. |
| `append(key, value)` | Adds the value, or each item of an array, to the end of a list property. Creates the list if it doesn't exist. |
| `prepend(key, value)` | Adds the value, or each item of an array, to the start of a list property. |
| `postInsert(key, value)` | Like `append()`, but skips values the list already contains. |
| `preInsert(key, value)` | Like `prepend()`, but skips values the list already contains. |
| `remove(key, value)` | Removes the value, or each item of an array, from a list property. |
| `unset(key)` | Removes the property from the user. |
| `clearAll()` | Removes every user property. You can't undo it. Ignores every other operation in the same `Identify` object. |

Each property can appear in only one operation per `Identify` object. The SDK silently ignores a second operation on the same key, so split conflicting operations across two `identify()` calls.

To set user properties as part of initialization, so they apply before the first autocaptured event, pass the `Identify` object to `init()`:

```ts
amplitude.init("YOUR_API_KEY", "user-12345", {
  identify: new amplitude.Identify().set("plan", "pro"),
});
```

The SDK sends this identify each time a new session starts, before the session's start event. The `identify` option requires Browser SDK 2.25.0 or later.

### Assign a user to a group

Groups let you analyze behavior at the account or organization level. Group analytics requires the Accounts add-on.

`setGroup()` assigns the current user to one or more groups of a type. The assignment persists on the user like a user property.

```ts
amplitude.setGroup("company", "Acme Inc");
amplitude.setGroup("team", ["engineering", "design"]);
```

To associate only one event with a group, without changing the user's groups, set `groups` on the event:

```ts
amplitude.track({
  event_type: "Report Exported",
  groups: { company: "Acme Inc" },
});
```

### Set group properties

Group properties describe the group, not the user. Use `groupIdentify()` with an `Identify` object. All `Identify` operations work the same way as they do for user properties. Updates apply only to events from that point onward.

```ts
const groupProperties = new amplitude.Identify()
  .set("plan", "enterprise")
  .set("seats", 250);

amplitude.groupIdentify("company", "Acme Inc", groupProperties);
```

### Track revenue

Build a `Revenue` object and pass it to `revenue()`. The SDK sends a `revenue_amount` event that Amplitude uses for revenue analysis.

```ts
const purchase = new amplitude.Revenue()
  .setProductId("com.example.pro-annual")
  .setPrice(99.99)
  .setQuantity(2)
  .setRevenueType("subscription")
  .setCurrency("USD")
  .setEventProperties({ coupon: "FALL26" });

amplitude.revenue(purchase);
```

Amplitude computes revenue as `price * quantity` unless you call `setRevenue()`, which sets the total directly. `quantity` defaults to `1`, and `setQuantity()` ignores values of `0` or less. `setPrice()` accepts negative values, for example for a refund. Revenue verification requires `setRevenueType()` and `setReceiptSig()`. For every setter, go to [Revenue](https://amplitude.com/docs/sdks/analytics/browser/browser-sdk-2/reference#revenue) in the API reference.

Amplitude converts revenue to your project's currency only when you set `currency`. For more on currency conversion and revenue event setup, go to [Track revenue](https://amplitude.com/docs/data/sources/instrument-track-revenue).

> **Tip:**
>
> To get the most from revenue data, also track products with the [product array](https://amplitude.com/docs/analytics/charts/cart-analysis) method.

### Track video playback

`trackVideo()` sends `Video Content Started` and `Video Content Stopped` events from an HTML `<video>` element or a [player.js](https://github.com/embedly/player.js)-compatible embedded player. This API is experimental and can change without notice. Requires Browser SDK 2.39.0 or later.

```ts
const video = document.querySelector("video");

if (video) {
  const stopTracking = amplitude.trackVideo(amplitude, video, {
    extraEventProperties: { placement: "homepage" },
  });

  // Later, when you no longer need video events:
  if (typeof stopTracking === "function") {
    stopTracking();
  }
}
```

Every event from one `trackVideo()` call carries the same `view_session_id`, a UUID the SDK generates, plus your `extraEventProperties`. `trackVideo()` returns an `Error` instead of a function if it can't attach to the player.

### Send events to more than one project

Each Amplitude project has its own API key. To send to two projects, create a second instance with `createInstance()` and give it a distinct `instanceName`.

```ts
amplitude.init("PRODUCTION_API_KEY");

const marketing = amplitude.createInstance();
marketing.init("MARKETING_API_KEY", {
  instanceName: "marketing",
});

amplitude.track("Signup Completed");
marketing.track("Signup Completed");
```

Each instance keeps its own user ID, device ID, session, queue, and plugins. Instances that share an API key share their identity cookie, so give each project its own key.

## Identity and sessions

Browser SDK 2 attaches three identifiers to every event: a device ID, an optional user ID, and a session ID. Amplitude uses them to merge events into users and sessions. The SDK stores all three, plus the opt-out flag, in a first-party cookie named `AMP_` followed by the first 10 characters of your API key, so they persist across page loads and subdomains.

### How the SDK chooses identifiers on load

During `init()`, the SDK takes each identifier from the first source that has a value.

#### Device ID

1. The `deviceId` option.
2. The `ampDeviceId` URL parameter, then the legacy `deviceId` URL parameter.
3. The `AMP_` cookie.
4. A cookie from the maintenance JavaScript SDK (`amp_` prefix).
5. A new UUID.

#### User ID

1. The `userId` argument or option.
2. The `AMP_` cookie.
3. A cookie from the maintenance JavaScript SDK.

#### Session ID

1. The `sessionId` option.
2. The `ampSessionId` URL parameter.
3. A session deferred by opt-out.
4. The `AMP_` cookie, if the last event was within `sessionTimeout`.
5. `Date.now()`.

The SDK ignores the URL parameters when an `ampTimestamp` parameter is present and earlier than the current time. Go to [Track users across domains](#track-users-across-domains).

### Set the user ID

Call `setUserId()` when a user signs in. Use a stable, unique identifier from your system, such as a database ID. Avoid email addresses if they can change.

```ts
amplitude.setUserId("user-12345");
```

The new user ID applies to every later event and persists in the cookie. User IDs and device IDs must be at least five characters, or Amplitude rejects the event. To allow shorter IDs, set `minIdLength` in `init()`.

If you know the user ID at page load, pass it to `init()` instead, so the first autocaptured events carry it:

```ts
amplitude.init("YOUR_API_KEY", "user-12345");
```

### Reset identity when a user signs out

Call `reset()` when a user signs out. It sets the user ID to `undefined` and generates a new device ID, so later activity on this browser doesn't merge into the signed-out user.

```ts
amplitude.reset();
```

`reset()` doesn't start a new session, clear user properties on Amplitude's servers, or change opt-out. Plugins can react to it through their `onReset()` hook.

### Set the device ID

The SDK generates a random UUID as the device ID. Set your own only when you already have a stable anonymous identifier, for example one shared with your server or a mobile app.

```ts
amplitude.setDeviceId("2f9d6a1e-4b7c-4f0e-9a51-0c3e8b7d5a12");
```

Use a value that's unique per device and at least five characters long. Two browsers with the same device ID merge into one user in Amplitude.

### Read and update identity together

`getIdentity()` returns the current device ID, user ID, and the user properties the SDK tracked in this page load. Requires Browser SDK 2.18.0 or later.

```ts
const { deviceId, userId, userProperties } = amplitude.getIdentity();
```

`setIdentity()` updates any combination of the three. When you include `userProperties`, the SDK sends an `$identify` event that sets each key with `$set`. `setIdentity()` requires Browser SDK 2.35.0 or later.

```ts
amplitude.setIdentity({
  userId: "user-12345",
  userProperties: { plan: "pro" },
});
```

`setIdentity()` doesn't unset properties you leave out of `userProperties`. To remove a user property, send `new amplitude.Identify().unset("key")`.

`userProperties` reflects only the `$set`, `$unset`, and `$clearAll` operations sent from this page load. It doesn't read user properties from Amplitude's servers.

### How sessions work

A session groups the events a user triggers in one visit. Its ID is the Unix timestamp, in milliseconds, of the session's first event.

- The SDK starts a new session when an event arrives more than `sessionTimeout` after the previous event. The default timeout is 30 minutes.
- Sessions continue across page loads and tabs on the same domain, because the last event time lives in the cookie.
- With session autocapture on (the default), the SDK sends `session_start` at the start of each session and `session_end` at the end of the previous one. Amplitude shows them as `[Amplitude] Start Session` and `[Amplitude] End Session`. The SDK sends `session_end` when the next session starts, not when the user leaves.
- With `autocapture.attribution.resetSessionOnNewCampaign` set to `true`, a new campaign also starts a new session.

To change the timeout, set `sessionTimeout` in milliseconds:

```ts
amplitude.init("YOUR_API_KEY", {
  sessionTimeout: 60 * 60 * 1000, // 1 hour
});
```

For how Amplitude counts session length and session metrics, go to [Track sessions](https://amplitude.com/docs/data/sources/instrument-track-sessions).

#### Keep a session alive without an event

`extendSession()` updates the last activity time without sending an event. Use it for activity the SDK can't observe, such as a user watching a long video.

```ts
amplitude.extendSession();
```

#### Start a session with a specific ID

`setSessionId()` ends the current session and starts one with the ID you pass. Pass a Unix timestamp in milliseconds.

```ts
amplitude.setSessionId(Date.now());
```

With session autocapture on, the SDK treats the ID as the session's last activity time. If you pass an ID that isn't a recent timestamp, the next event looks more than `sessionTimeout` late, and the SDK replaces your ID with a new session. `setSessionId()` does nothing if the ID equals the current session ID.

### Track users across domains

The `AMP_` cookie covers one registrable domain and its subdomains, such as `www.example.com` and `app.example.com`. To keep one anonymous user and session across two different domains, such as `example.com` and `example-shop.com`, pass the identifiers in the link URL. The `ampDeviceId` and `ampSessionId` parameters require Browser SDK 2.8.0 or later.

1. On the source domain, read the current IDs and add them to the link:

   ```ts
   const url = new URL("https://www.example-shop.com/cart");
   url.searchParams.set("ampDeviceId", amplitude.getDeviceId() ?? "");
   url.searchParams.set("ampSessionId", String(amplitude.getSessionId()));
   url.searchParams.set("ampTimestamp", String(Date.now() + 60_000));
   window.location.assign(url.toString());
   ```
2. On the destination domain, initialize the SDK without `deviceId` or `sessionId` options. The SDK reads the parameters automatically.

   ```ts
   amplitude.init("YOUR_API_KEY");
   ```

`ampTimestamp` limits how long the link works. The SDK uses `ampDeviceId` and `ampSessionId` only while `ampTimestamp` is later than the current time. Without `ampTimestamp`, the parameters never expire, so a shared or bookmarked link can merge unrelated visitors into one device. `ampTimestamp` requires Browser SDK 2.21.1 or later.

### Opt users out of tracking

`setOptOut(true)` stops the SDK from tracking or uploading events for this browser. The setting persists in the `AMP_` cookie.

```ts
amplitude.setOptOut(true);

// Later, after the user consents:
amplitude.setOptOut(false);
```

While opt-out is on:

- Every tracking call resolves immediately with code `0` and `Event skipped due to optOut config`.
- The SDK doesn't start sessions. If `init()` runs while opted out, the SDK defers the session and starts it when you call `setOptOut(false)`.
- Page view and attribution tracking start when opt-out turns off.

To start opted out until a user consents, pass `optOut: true` to `init()`. For consent banners, deferred initialization, and cookie-free storage, go to [Cookies and consent management](https://amplitude.com/docs/sdks/analytics/browser/cookies-and-consent-management).

### Choose where the SDK stores identity

`identityStorage` controls where the SDK keeps the device ID, user ID, session ID, and opt-out flag.

| Value | Storage | Shared across subdomains | Persists after the tab closes |
| --- | --- | --- | --- |
| `"cookie"` (default) | First-party cookie | Yes | Yes, until `cookieOptions.expiration` (365 days) |
| `"localStorage"` | `localStorage` | No | Yes |
| `"sessionStorage"` | `sessionStorage` | No | No |
| `"none"` | Memory only | No | No, and a reload creates a new device and session |

```ts
amplitude.init("YOUR_API_KEY", {
  identityStorage: "localStorage",
});
```

By default, the SDK writes the cookie on the highest domain it can, such as `.example.com`, so subdomains share identity. It finds that domain by writing and deleting an `AMP_TLDTEST` cookie at each level. To pin the domain or change other cookie attributes, set `cookieOptions`. Go to [Cookie options](https://amplitude.com/docs/sdks/analytics/browser/browser-sdk-2/reference#cookie-options).

## Autocapture

Autocapture is the set of plugins Browser SDK 2 installs during `init()` to track common browser events without code. You control them with the `autocapture` option, and your Amplitude project's remote configuration can override your settings per feature. For a general introduction, go to [Autocapture](https://amplitude.com/docs/data/autocapture).

### Choose a starting point

| `autocapture` value | Features on |
| --- | --- |
| Omitted | The default set: attribution, page views, sessions, form interactions, file downloads, and page URL enrichment. |
| `true` | The default set, plus element interactions, frustration interactions, network tracking, and web vitals. Performance tracking stays off. |
| `false` | None. |
| An object | The default set, with your changes. Set a feature to `false` to turn it off, or to `true` or an options object to turn it on. |

```ts
amplitude.init("YOUR_API_KEY", {
  autocapture: {
    elementInteractions: true,
    fileDownloads: false,
    pageViews: { trackHistoryChanges: "pathOnly" },
  },
});
```

In a Chrome extension, the SDK turns off the default set. Set each feature you want to `true`. This [default changed in 2.42.1](https://amplitude.com/docs/sdks/analytics/browser/browser-sdk-2-release-notes#2421).

`autocapture` replaces the deprecated `defaultTracking` option. If you set both, `autocapture` wins.

### Features and events

| Feature | Option | Default | Events |
| --- | --- | --- | --- |
| [Marketing attribution](#marketing-attribution) | `attribution` | On | `$identify` with campaign user properties, campaign event properties, optional `[Amplitude] Attribution` |
| [Page views](#page-views) | `pageViews` | On | `[Amplitude] Page Viewed` |
| [Sessions](#sessions) | `sessions` | On | `session_start`, `session_end` |
| [Form interactions](#form-interactions) | `formInteractions` | On | `[Amplitude] Form Started`, `[Amplitude] Form Submitted` |
| [File downloads](#file-downloads) | `fileDownloads` | On | `[Amplitude] File Downloaded` |
| [Page URL enrichment](#page-url-enrichment) | `pageUrlEnrichment` | On | No event. Adds page properties to every event. |
| [Element interactions](#element-interactions) | `elementInteractions` | Off | `[Amplitude] Element Clicked`, `[Amplitude] Element Changed` |
| [Frustration interactions](#frustration-interactions) | `frustrationInteractions` | Off | `[Amplitude] Rage Click`, `[Amplitude] Dead Click`, `[Amplitude] Error Click`, `[Amplitude] Thrashed Cursor` |
| [Network tracking](#network-tracking) | `networkTracking` | Off | `[Amplitude] Network Request` |
| [Web vitals](#web-vitals) | `webVitals` | Off | `[Amplitude] Web Vitals` |
| [Performance tracking](#performance-tracking) | `performanceTracking` | Off | `[Amplitude] Main Thread Block` |

For every sub-option and default, go to [Autocapture options](https://amplitude.com/docs/sdks/analytics/browser/browser-sdk-2/reference#autocapture-options) in the configuration reference.

### Remote configuration overrides local settings

By default, `init()` fetches your project's remote configuration from Amplitude before it installs plugins. You edit remote configuration in _Data > Settings > Autocapture_. For each feature that remote configuration sets, its value replaces yours. Features it doesn't set keep your local value. Remote configuration requires Browser SDK 2.10.0 or later, and its [default changed to on in 2.16.1](https://amplitude.com/docs/sdks/analytics/browser/browser-sdk-2-release-notes#2161).

```ts
// Local configuration
amplitude.init("YOUR_API_KEY", {
  autocapture: { pageViews: true, elementInteractions: true },
});

// Remote configuration: elementInteractions off, frustrationInteractions on
// Result: pageViews on, elementInteractions off, frustrationInteractions on
```

Remote configuration can turn on a feature even when you set `autocapture: false`. To make local settings final, turn off the fetch:

```ts
amplitude.init("YOUR_API_KEY", {
  autocapture: false,
  remoteConfig: { fetchRemoteConfig: false },
});
```

The fetch goes to `https://sr-client-cfg.amplitude.com/config` (or `sr-client-cfg.eu.amplitude.com` for EU). The SDK caches the response in the browser and applies whichever arrives first, the cache or the network. If an ad blocker or a Content Security Policy blocks the request, the SDK uses the cached copy, or your local settings when no cache exists. To route it through your own server, set `remoteConfig.serverUrl` (Browser SDK 2.28.0 or later).

### Marketing attribution

Attribution reads UTM parameters, click IDs, and the referrer when the page loads. It records them in two independent ways, controlled by `attribution.trackingMethod`. Both run by default. The `eventProperty` method requires Browser SDK 2.40.0 or later, and the [default changed to both methods in 2.45.0](https://amplitude.com/docs/sdks/analytics/browser/browser-sdk-2-release-notes#2450).

| Method | What it does | Use it for |
| --- | --- | --- |
| `userProperty` | Sends an `$identify` event that sets the campaign as user properties, and sets first-touch `initial_*` properties once. It sends the identify at the start of a session, or mid-session when the campaign changes. It stores the last campaign in an `AMP_MKTG_` cookie. The identify also unsets campaign fields that the new campaign doesn't have, such as UTM parameters, click IDs, and referrer fields. | First-touch and last-touch attribution on the user |
| `eventProperty` | Adds the current page's campaign parameters to the event properties of every event. It updates on page load and on SPA navigation. | Multi-touch attribution with [persisted properties](https://amplitude.com/docs/data/persisted-properties) |

The SDK reads these parameters: `utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, `utm_content`, `utm_id`, `referrer`, `referring_domain`, `dclid`, `fbclid`, `gbraid`, `gclid`, `ko_click_id`, `li_fat_id`, `msclkid`, `rdt_cid`, `ttclid`, `twclid`, and `wbraid`.

```ts
amplitude.init("YOUR_API_KEY", {
  autocapture: {
    attribution: {
      trackingMethod: "userProperty",
      excludeReferrers: [/example\.com$/, "checkout.partner.com"],
      resetSessionOnNewCampaign: true,
    },
  },
});
```

These options apply only to `userProperty` tracking:

- `excludeReferrers` ignores campaigns from matching referring domains. By default it excludes your own domain and its subdomains. Setting it replaces the default, so include your own domain.
- `excludeInternalReferrers` ignores referrers on the same domain as the page. Set `true` (or `{ condition: "always" }`) to always ignore them, or `{ condition: "ifEmptyCampaign" }` to ignore them only when the URL has no UTM parameters or click IDs. Requires Browser SDK 2.36.0 or later.
- `initialEmptyValue` is the value for `initial_*` properties that have no campaign value. The default is `"EMPTY"`.
- `resetSessionOnNewCampaign` starts a new session when the campaign changes.

`fallbackAttributionEvent` applies only to `eventProperty` tracking. Set it to `true` to send an `[Amplitude] Attribution` event on every page view, so campaign data arrives even if the user triggers no other event.

#### Single-page apps lose campaign context

In a single-page app, `document.referrer` doesn't change after the first load, and routers often strip the query string. Two consequences:

- `userProperty` tracking reads the campaign only during `init()`. A campaign that appears later through `pushState` doesn't update user properties.
- `eventProperty` tracking replaces the campaign on each navigation. After a user moves from `/?utm_source=newsletter` to `/pricing`, later events have no `utm_source`, but they keep the original `referrer`.

Initialize the SDK before your router rewrites the URL, and keep campaign parameters on server-side redirects. To drop the referrer after the landing page, add an enrichment plugin:

```ts
const landingUrl = location.href;

amplitude.add({
  name: "drop-referrer-after-landing",
  execute: async (event) => {
    if (location.href !== landingUrl && event.event_properties) {
      delete event.event_properties.referrer;
      delete event.event_properties.referring_domain;
    }
    return event;
  },
});
```

### Page views

The SDK sends `[Amplitude] Page Viewed` when it initializes, and again when the URL changes through `history.pushState()` or the `popstate` event (back, forward, and hash changes). `history.replaceState()` doesn't trigger a page view.

> **Warning:** Multi-step forms
>
> A multi-step form that updates in place without changing the URL produces one page view. To track each step, add a hash to the URL for each step, for example with [Google Tag Manager](https://support.google.com/tagmanager/answer/7679410?hl=en).

Each page view includes:

| Property | Value |
| --- | --- |
| `[Amplitude] Page Domain` | `location.hostname` |
| `[Amplitude] Page Location` | `location.href`, decoded |
| `[Amplitude] Page Path` | `location.pathname`, decoded |
| `[Amplitude] Page Title` | `document.title`, or `*****` when the `<title>` element has a `data-amp-mask` attribute |
| `[Amplitude] Page URL` | `location.href` without the query string |
| `[Amplitude] Page View ID` | A UUID for this page view, shared with element interaction events on the page |
| `[Amplitude] Page Counter` | The number of page views so far in this session |
| Campaign parameters | UTM parameters, click IDs, and referrer fields, when present |

Options:

- `trackOn` controls when to send page views. Omit it to send on every load and URL change. Set `"attribution"` to send a page view only when attribution sends a campaign. Set a function that returns `true` to send.
- `trackHistoryChanges` controls which URL changes count. The default sends a page view on any change to the URL, including the query string and hash. Set `"pathOnly"` to ignore changes that keep the same origin and path.
- `eventType` renames the event.

```ts
amplitude.init("YOUR_API_KEY", {
  autocapture: {
    pageViews: {
      trackOn: () => location.pathname.startsWith("/app"),
      trackHistoryChanges: "pathOnly",
    },
  },
});
```

To add your own properties to page view events, use an enrichment plugin. For a working example, go to [page-view-tracking-enrichment](https://github.com/amplitude/Amplitude-TypeScript/blob/main/examples/plugins/page-view-tracking-enrichment/index.ts) on GitHub.

#### Page titles in single-page apps

The SDK reads `document.title` right after `pushState()` returns. Routers in Angular, SvelteKit, React Router, and others often set the title after the URL changes, so the page view records the previous page's title. To fix this, turn off page views and track them from your router after it sets the title:

```ts
amplitude.init("YOUR_API_KEY", {
  autocapture: { pageViews: false },
});

// Call from your router's navigation-end hook, after the title updates.
function trackPageView() {
  amplitude.track("[Amplitude] Page Viewed", {
    "[Amplitude] Page Domain": location.hostname,
    "[Amplitude] Page Location": location.href,
    "[Amplitude] Page Path": location.pathname,
    "[Amplitude] Page Title": document.title,
    "[Amplitude] Page URL": location.href.split("?")[0],
  });
}
```

Page views you track yourself don't include `[Amplitude] Page Counter` or `[Amplitude] Page View ID`.

### Sessions

The SDK sends `session_start` when a session begins and `session_end` for the previous session when the next one begins. Amplitude displays them as `[Amplitude] Start Session` and `[Amplitude] End Session`. With `sessions: false`, the SDK still assigns session IDs to events. For session rules, go to [Identity and sessions](#how-sessions-work).

### Form interactions

The SDK tracks `<form>` elements:

- `[Amplitude] Form Started` on the first change to any field in the form.
- `[Amplitude] Form Submitted` on submit. If the user submits without changing a field, the SDK sends `Form Started` first.

Both events include `[Amplitude] Form ID`, `[Amplitude] Form Name`, and `[Amplitude] Form Destination` (the form's `action` URL).

Forms with the `novalidate` attribute fire `submit` even when the input is invalid. To decide which submits count, pass `shouldTrackSubmit` (Browser SDK 2.34.0 or later):

```ts
amplitude.init("YOUR_API_KEY", {
  autocapture: {
    formInteractions: {
      shouldTrackSubmit: (event) =>
        (event.target as HTMLFormElement).checkValidity(),
    },
  },
});
```

### File downloads

The SDK sends `[Amplitude] File Downloaded` when a user clicks an `<a>` element whose URL path ends in one of these extensions: `pdf`, `xls`, `xlsx`, `doc`, `docx`, `txt`, `rtf`, `csv`, `exe`, `key`, `pps`, `ppt`, `pptx`, `7z`, `pkg`, `rar`, `gz`, `zip`, `avi`, `mov`, `mp4`, `mpg`, `mpeg`, `wmv`, `mid`, `midi`, `mp3`, `wav`, or `wma`. It watches links added after load too.

Properties: `[Amplitude] File Extension`, `[Amplitude] File Name` (the URL path), `[Amplitude] Link ID`, `[Amplitude] Link Text`, and `[Amplitude] Link URL`.

### Page URL enrichment

Page URL enrichment adds page context to every event, including custom ones: `[Amplitude] Page Domain`, `[Amplitude] Page Location`, `[Amplitude] Page Path`, `[Amplitude] Page Title`, `[Amplitude] Page URL`, `[Amplitude] Previous Page Location`, and `[Amplitude] Previous Page Type`. It doesn't overwrite a property the event already has. For details, go to [Page URL enrichment plugin](https://amplitude.com/docs/sdks/analytics/browser/page-url-enrichment-plugin). Requires Browser SDK 2.22.0 or later, and the [default changed to on in 2.29.0](https://amplitude.com/docs/sdks/analytics/browser/browser-sdk-2-release-notes#2290).

If you turn off Autocapture, you can still add the plugin yourself:

```ts
import { pageUrlEnrichmentPlugin } from "@amplitude/plugin-page-url-enrichment-browser";

amplitude.add(pageUrlEnrichmentPlugin());
amplitude.init("YOUR_API_KEY", { autocapture: false });
```

### Element interactions

Element interactions send `[Amplitude] Element Clicked` and `[Amplitude] Element Changed` for interactive elements. [Visual labeling](https://amplitude.com/docs/data/visual-labeling) and [Zoning Insights](https://amplitude.com/docs/zoning-insights) depend on these events. For the data collected and how text masking works, go to [Autocapture privacy and security](https://amplitude.com/docs/data/autocapture#privacy-and-security).

By default the SDK tracks clicks and changes on `a`, `button`, `input`, `select`, `textarea`, `label`, `video`, `audio`, `[contenteditable="true"]`, `[data-amp-default-track]`, and `.amp-default-track`. It also tracks clicks on `div`, `span`, and `h1` through `h6` when the click changes the DOM or navigates.

```ts
import * as amplitude from "@amplitude/analytics-browser";

amplitude.init("YOUR_API_KEY", {
  autocapture: {
    elementInteractions: {
      cssSelectorAllowlist: [
        ...amplitude.Types.DEFAULT_CSS_SELECTOR_ALLOWLIST,
        ".track-me",
      ],
      pageUrlExcludelist: [/\/admin\//],
      dataAttributePrefix: "data-analytics-",
    },
  },
});
```

- `cssSelectorAllowlist` replaces the default list. Spread `DEFAULT_CSS_SELECTOR_ALLOWLIST` to extend it. Import it as `amplitude.Types.DEFAULT_CSS_SELECTOR_ALLOWLIST` from `@amplitude/analytics-browser`; `@amplitude/plugin-autocapture-browser` doesn't export it.
- `actionClickAllowlist` replaces the `div`, `span`, and heading list for clicks that change the page. Set `[]` when you narrow `cssSelectorAllowlist`, so only your selectors count.
- `pageUrlAllowlist` and `pageUrlExcludelist` limit tracking by page URL. The exclude list wins.
- `shouldTrackEventResolver` decides per element. When you set it, the SDK ignores the allowlists.
- `dataAttributePrefix` captures matching `data-` attributes as properties. The default is `data-amp-track-`, so `data-amp-track-plan="pro"` becomes the `plan` property.
- `maskTextRegex` masks text that matches your patterns, in addition to the SDK's built-in masking.
- `shadowDomEnabled` (experimental) captures interactions inside open shadow roots, up to `maxShadowDomDepth` boundaries (default `1`, maximum `10`). After it turns on, it stays on until the next full page load.

Event properties include `[Amplitude] Element ID`, `[Amplitude] Element Class`, `[Amplitude] Element Tag`, `[Amplitude] Element Text` (clicks only), `[Amplitude] Element Href` (clicks only), `[Amplitude] Element Position Left`, `[Amplitude] Element Position Top`, `[Amplitude] Element Hierarchy`, `[Amplitude] Element Path` (a CSS selector for the element), `[Amplitude] Element Attributes`, `[Amplitude] Element Aria Label`, `[Amplitude] Element Parent Label`, `[Amplitude] Viewport Height`, `[Amplitude] Viewport Width`, `[Amplitude] Page URL`, and `[Amplitude] Page Title`.

### Frustration interactions

`frustrationInteractions: true` turns on rage clicks and dead clicks. Error clicks and thrashed cursor are off until you turn them on individually. Frustration interactions require Browser SDK 2.24.0 or later.

| Signal | Event | Definition |
| --- | --- | --- |
| Rage click | `[Amplitude] Rage Click` | Four or more clicks on the same element, within 50 pixels, in one second. Tracked on any element. |
| Dead click | `[Amplitude] Dead Click` | A click on an interactive element that changes neither the DOM nor the URL within three seconds. |
| Error click | `[Amplitude] Error Click` | A click on an interactive element followed within two seconds by a JavaScript error, console error, or unhandled promise rejection. Properties include `[Amplitude] Kind`, `[Amplitude] Message`, `[Amplitude] Stack`, `[Amplitude] Filename`, `[Amplitude] Line Number`, `[Amplitude] Column Number`, and the clicked element's properties, such as `[Amplitude] Element Text`. |
| Thrashed cursor | `[Amplitude] Thrashed Cursor` | Rapid back-and-forth pointer movement: 20 direction changes (minimum 5) within 2,000 ms (maximum 4,000). The SDK counts X-axis and Y-axis direction changes separately. |

```ts
amplitude.init("YOUR_API_KEY", {
  autocapture: {
    frustrationInteractions: {
      rageClicks: true,
      deadClicks: { cssSelectorAllowlist: ["button", "a"] },
      errorClicks: true,
      thrashedCursor: { directionChanges: 12, threshold: 1500 },
    },
  },
});
```

Dead clicks and error clicks use the interactive-element lists `DEFAULT_DEAD_CLICK_ALLOWLIST` and `DEFAULT_ERROR_CLICK_ALLOWLIST` unless you set `cssSelectorAllowlist`. For the selectors, go to [frustration-interactions.ts](https://github.com/amplitude/Amplitude-TypeScript/blob/main/packages/analytics-core/src/types/frustration-interactions.ts) on GitHub.

Each signal also accepts `pageUrlAllowlist`, `pageUrlExcludelist`, `shouldTrackEventResolver`, `dataAttributePrefix`, and `maskTextRegex` at the `frustrationInteractions` level.

### Network tracking

Network tracking sends `[Amplitude] Network Request` for `fetch` and `XMLHttpRequest` calls. By default it captures only responses with status `500` through `599`, and it ignores requests to Amplitude. Requires Browser SDK 2.17.0 or later.

```ts
amplitude.init("YOUR_API_KEY", {
  autocapture: {
    networkTracking: {
      ignoreHosts: ["*.example-cdn.com"],
      captureRules: [
        { statusCodeRange: "400-599" },
        {
          urls: [/https:\/\/api\.example\.com\/orders/],
          methods: ["POST"],
          statusCodeRange: "0,200-599",
          responseHeaders: true,
          responseBody: { allowlist: ["order/id", "order/status"] },
        },
      ],
    },
  },
});
```

The SDK checks `captureRules` from last to first and applies the first rule that matches the URL, so put specific rules after general ones. Each rule accepts:

- `urls` (string or RegExp, supports `*`) or the older `hosts`. Omit both to match every URL.
- `methods`, for example `["POST", "PUT"]`. Default: all.
- `statusCodeRange`, a comma-separated list of codes and ranges. Default: `"500-599"`. `0` matches requests that failed without a response.
- `requestHeaders` and `responseHeaders`: `true` captures a fixed set of non-sensitive headers, such as `content-type` and `cache-control`. An array captures exactly the names you list.
- `requestBody` and `responseBody`: JSON bodies only. `allowlist` lists JSON pointers to keep (`*` matches one key, `**` any depth). `excludelist` removes keys the allowlist matched. The SDK captures no body content unless you set an allowlist.

For example, with this body:

```json
{ "a": "A", "b": { "c": "C", "d": { "e": "E", "f": "F" } }, "g": "G" }
```

| `allowlist` | `excludelist` | Captured |
| --- | --- | --- |
| `["a"]` | None | `{ "a": "A" }` |
| `["*"]` | None | `{ "a": "A", "g": "G" }` |
| `["b/c"]` | None | `{ "b": { "c": "C" } }` |
| `["b/d/*"]` | None | `{ "b": { "d": { "e": "E", "f": "F" } } }` |
| `["b/**"]` | None | `{ "b": { "c": "C", "d": { "e": "E", "f": "F" } } }` |
| `["**/e"]` | None | `{ "b": { "d": { "e": "E" } } }` |
| `["b/**"]` | `["b/d/f"]` | `{ "b": { "c": "C", "d": { "e": "E" } } }` |

Event properties: `[Amplitude] URL` (with sensitive parts masked), `[Amplitude] URL Query`, `[Amplitude] URL Fragment`, `[Amplitude] Request Method`, `[Amplitude] Status Code`, `[Amplitude] Error Code`, `[Amplitude] Error Message`, `[Amplitude] Start Time`, `[Amplitude] Completion Time`, `[Amplitude] Duration`, `[Amplitude] Request Body Size`, `[Amplitude] Response Body Size`, and, when configured, `[Amplitude] Request Body`, `[Amplitude] Response Body`, `[Amplitude] Request Headers`, and `[Amplitude] Response Headers`.

### Web vitals

Web vitals sends one `[Amplitude] Web Vitals` event per page load, when the tab first becomes hidden. It includes `[Amplitude] LCP`, `[Amplitude] FCP`, `[Amplitude] INP`, `[Amplitude] CLS`, and `[Amplitude] TTFB` when the browser reports them, plus the page domain, location, path, title, and URL. For metric definitions, go to web.dev: [LCP](https://web.dev/articles/lcp), [FCP](https://web.dev/articles/fcp), [INP](https://web.dev/articles/inp), [CLS](https://web.dev/articles/cls), and [TTFB](https://web.dev/articles/ttfb). Requires Browser SDK 2.20.0 or later.

In single-page apps, set `webVitals: { reportSoftNav: true }` (experimental) to also send one event per soft navigation. This requires the Soft Navigations API (Chromium 151 and later). Other browsers report only the initial load.

### Performance tracking

Performance tracking sends `[Amplitude] Main Thread Block` when the main thread stays busy for at least 100 ms. It uses the Long Animation Frames API where available and falls back to Long Tasks. `autocapture: true` doesn't turn it on. Requires Browser SDK 2.41.0 or later.

```ts
amplitude.init("YOUR_API_KEY", {
  autocapture: {
    performanceTracking: {
      mainThreadBlock: { durationThreshold: 200 },
    },
  },
});
```

## Data delivery

Browser SDK 2 doesn't send each event as it's tracked. It saves events to a queue in `localStorage`, uploads them in batches, and retries failed uploads. This section covers each setting that changes that behavior. The defaults suit most sites. Change them when events go missing on navigation, when a network blocks Amplitude's domain, or when you need a specific data region.

### How the SDK uploads events

1. `track()` adds the event to an in-memory queue and writes the queue to `localStorage` under `AMP_unsent_` plus the first 10 characters of your API key.
2. `flushIntervalMillis` (default `1000`) after the first event enters the queue, the SDK sends the whole queue, split into requests of up to `flushQueueSize` (default `30`) events. Requests go out one at a time to keep events in order. A full queue doesn't trigger an early upload.
3. On a `200` response, the SDK removes the events from the queue and storage.
4. On a failure, the SDK retries. After network errors and `5xx` responses, the wait grows by 1 second with each attempt. Throttled events (`429`) wait 30 seconds. A `413` halves `flushQueueSize`. After `flushMaxRetries` (default `5`) attempts, the SDK drops the event.
5. On the next page load, the SDK sends any events still in `localStorage`.

`localStorage` holds at most 1,000 unsent events. If the queue grows past that, for example during a long offline period, the SDK logs `Failed to save N events because the queue length exceeded 1000` and doesn't persist the extra events. If `localStorage` isn't available, the SDK keeps the queue in memory only and logs a warning.

```ts
amplitude.init("YOUR_API_KEY", {
  flushIntervalMillis: 5000,
  flushQueueSize: 50,
  flushMaxRetries: 3,
});
```

To send the queue now, call `flush()`. It sends every queued event once, without retries.

```ts
await amplitude.flush().promise;
```

### Tune upload performance

`track()` doesn't wait for the network. The SDK queues the event and uploads in the background, so most tuning is a tradeoff between request count and delay:

- **Fewer requests**: raise `flushIntervalMillis`. Events wait longer before they upload. If the page closes first, they stay in `localStorage` until the user's next visit.
- **Faster delivery**: lower `flushIntervalMillis`. The SDK sends more, smaller requests.
- **Larger requests**: raise `flushQueueSize` when a single flush often holds more than 30 events. Each request carries more events, so a busy queue needs fewer requests. If a request is too large, Amplitude returns `413` and the SDK halves `flushQueueSize`.
- **High-volume sources**: set `useBatch: true` to send to the Batch Event Upload API, which has higher rate limits. It doesn't reduce the number of requests.

For current bundle size, check the [npm package page](https://www.npmjs.com/package/@amplitude/analytics-browser) or [BundlePhobia](https://bundlephobia.com/package/@amplitude/analytics-browser).

### Choose a data region

Set `serverZone` to the data center of your Amplitude organization. The default is `US`.

```ts
amplitude.init("YOUR_API_KEY", {
  serverZone: "EU",
});
```

| `serverZone` | Event endpoint | Remote configuration |
| --- | --- | --- |
| `US` | `https://api2.amplitude.com/2/httpapi` | `https://sr-client-cfg.amplitude.com/config` |
| `EU` | `https://api.eu.amplitude.com/2/httpapi` | `https://sr-client-cfg.eu.amplitude.com/config` |

`useBatch: true` switches to the [Batch Event Upload API](https://amplitude.com/docs/apis/analytics/batch-event-upload) (`/batch`), which has higher rate limits for high-volume sources. It doesn't change how the SDK batches events in the browser.

### Allow Amplitude in a Content Security Policy

If your site sends a `Content-Security-Policy` header, allow Amplitude's domains:

```text
script-src  https://*.amplitude.com;
connect-src https://*.amplitude.com;
```

You need `script-src` only when you load the SDK from Amplitude's CDN with the script loader. `connect-src` covers event uploads, remote configuration, and SDK diagnostics. If you proxy all traffic through your own domain, allow that domain instead.

### Send events when the page unloads

Browsers cancel in-flight requests when a page unloads, so you can lose events tracked right before a navigation, such as a login that redirects.

The default `fetch` transport sets `keepalive` on requests with bodies of 16 KB or less, so the browser finishes them after the page unloads. Keepalive keeps compression, custom headers, responses, and retries. Browsers share a 64 KB keepalive budget across all in-flight requests on a page, which is why the SDK sends larger bodies without it. The SDK measures the body after compression. If the browser rejects a keepalive request because the budget is full, the SDK retries on the next flush. Browsers that don't support `keepalive` ignore the flag and send a normal request.

For bodies larger than 16 KB, switch to the `beacon` transport when the page hides:

```ts
window.addEventListener("pagehide", () => {
  amplitude.setTransport("beacon");
  amplitude.flush();
});
```

`sendBeacon` hands the request to the browser and returns immediately. It has these limits:

- No response, so the SDK can't retry failures. It treats a request the browser accepts as successful.
- No custom headers, so no compression.
- No ordering guarantee between requests.

To turn off keepalive, for example to debug a keepalive-specific failure, set `enableKeepalive: false`:

```ts
amplitude.init("YOUR_API_KEY", {
  transport: { type: "fetch", enableKeepalive: false },
});
```

### Choose a transport

| `transport` | API | Keepalive | Compression | Custom headers | Retries |
| --- | --- | --- | --- | --- | --- |
| `"fetch"` (default) | `fetch()` | Yes, bodies of 16 KB or less | Yes | Yes | Yes |
| `"xhr"` | `XMLHttpRequest` | No | Yes | Yes | Yes |
| `"beacon"` | `navigator.sendBeacon()` | Not applicable | No | No | No |

Pass a string, or an object to set options:

```ts
amplitude.init("YOUR_API_KEY", {
  transport: {
    type: "fetch",
    headers: { "X-Request-Source": "web" },
    referrerPolicy: "strict-origin-when-cross-origin",
  },
});
```

- `headers` adds HTTP headers to each upload (`fetch` and `xhr`). The SDK always sets `Content-Type` and `Accept`, and your headers can't replace them. Custom headers make the request non-simple, so the server must answer a CORS preflight. Amplitude's endpoints do. Check your proxy. Requires Browser SDK 2.33.0 or later.
- `referrerPolicy` sets the `fetch` referrer policy for uploads. Requires Browser SDK 2.44.0 or later.
- `enableKeepalive` turns `fetch` keepalive on or off. It's on by default, a [default that changed in 2.43.0](https://amplitude.com/docs/sdks/analytics/browser/browser-sdk-2-release-notes#2430).

To change the transport after `init()`, call `setTransport()`.

### Compress request bodies

When the browser supports `CompressionStream`, the SDK gzip-compresses request bodies of 2 KB or more and sets `Content-Encoding: gzip`. This is always on for Amplitude's endpoints. For a custom `serverUrl`, it's off unless you set `enableRequestBodyCompression: true`, because your server must accept gzip bodies. Compression requires Browser SDK 2.36.0 or later, and the [default changed to on for Amplitude's endpoints in 2.42.0](https://amplitude.com/docs/sdks/analytics/browser/browser-sdk-2-release-notes#2420).

```ts
amplitude.init("YOUR_API_KEY", {
  serverUrl: "https://collect.example.com/amplitude",
  enableRequestBodyCompression: true,
});
```

Chrome and Edge 80, Firefox 113, Safari 16.4, and later versions support `CompressionStream`. Older browsers send uncompressed bodies. The `beacon` transport never compresses.

### Send events through a proxy

Ad blockers and some corporate networks block `*.amplitude.com`. A proxy on your own domain avoids that. Point `serverUrl` at the proxy, and forward each request body unchanged to the Amplitude endpoint for your region.

```ts
amplitude.init("YOUR_API_KEY", {
  serverUrl: "https://collect.example.com/amplitude",
  remoteConfig: {
    serverUrl: "https://collect.example.com/amplitude-config",
  },
});
```

`serverUrl` overrides `serverZone` and `useBatch` for event uploads. `remoteConfig.serverUrl` routes the remote configuration fetch separately.

Your proxy must:

- Answer `OPTIONS` preflight requests and return `Access-Control-Allow-Origin` for your site. Without it, the browser blocks the request and the console shows `has been blocked by CORS policy`.
- Forward the request body and `Content-Encoding` header unchanged, or decompress before forwarding.
- Forward the client's IP address, for example in `X-Forwarded-For`, if you want Amplitude to derive location from it. Otherwise Amplitude records the proxy's IP.
- Return Amplitude's status code and JSON body unchanged. The SDK reads them to decide whether to retry.

For setup examples, go to [Proxy requests](https://amplitude.com/docs/sdks/analytics/proxy-requests).

### Work offline

The SDK listens for the browser's `online` and `offline` events. While `navigator.onLine` is `false`, it keeps events in the queue and doesn't attempt uploads. When the browser comes back online, it uploads the queue after `flushIntervalMillis`. Requires Browser SDK 2.4.0 or later.

Keep `flushIntervalMillis` well above a few milliseconds. With a value such as `1`, the upload starts almost the moment the `online` event fires, often before the connection settles, and Chrome can fail it with `ERR_NETWORK_CHANGED`. The SDK retries the failed upload.

To control connectivity yourself, for example in a webview that reports `onLine` incorrectly, turn off the built-in check:

```ts
amplitude.init("YOUR_API_KEY", {
  offline: amplitude.Types.OfflineDisabled,
});
```

With `OfflineDisabled`, the SDK always attempts uploads and relies on retries.

### Store unsent events somewhere else

`storageProvider` replaces `localStorage` for the unsent-event queue. Pass any object that implements the `Storage` interface (`isEnabled`, `get`, `getRaw`, `set`, `remove`, `reset`), or an in-memory store to keep events out of persistent storage:

```ts
const memoryQueue = new Map<string, amplitude.Types.Event[]>();

amplitude.init("YOUR_API_KEY", {
  storageProvider: {
    isEnabled: async () => true,
    get: async (key) => memoryQueue.get(key),
    getRaw: async (key) => JSON.stringify(memoryQueue.get(key)),
    set: async (key, value) => void memoryQueue.set(key, value),
    remove: async (key) => void memoryQueue.delete(key),
    reset: async () => memoryQueue.clear(),
  },
});
```

With memory-only storage, events still queued when the page unloads are lost. Identity storage is separate. Go to [Choose where the SDK stores identity](#choose-where-the-sdk-stores-identity).

