Set up › Install

Sequential SDK (sequential.js)

One script tag for analytics and session replay, served from Sequential's CDN, with one key. The recommended install for a site with no analytics SDK yet.

Install with your AI agent

Copy a complete instruction for a coding agent (Claude Code, Cursor, Copilot…) with your repo open. It says how to find an existing install, what to change, and how to check events arrive, and it includes this page.

Keys are placeholders; the agent will ask for them. Sign in to fill in your project's keys.

On this page

Install

One tag for analytics and session replay, served from Sequential's CDN — no third-party origin in your page, and no second key to find. Paste it once into your site's <head>, or into a Google Tag Manager Custom HTML tag that fires on All Pages. Nothing else to change. In a single-page app, add it once, to the HTML shell (index.html, app.html, the root layout).

html
<!-- Sequential · analytics and session replay -->
<script>
(function(w){if(w.sequential)return;var q=[],s=w.sequential={_q:q},
m=['capture','identify','account','register','unregister','reset','optOut','optIn','captureException'];
for(var i=0;i<m.length;i++)(function(n){s[n]=function(){q.push([n,arguments])}})(m[i]);
s.flush=function(){q.push(['flush',arguments]);return typeof Promise=='function'?Promise.resolve():{then:function(f){if(f)f()}}}})(window);
</script>
<!-- Optional: add data-release="<your app version>" to the tag below to put the build on every event. -->
<script async src="https://cdn.usesequential.com/sdk/sequential/v1/sequential.js"
        data-key="YOUR_SEQUENTIAL_INGEST_KEY"
        data-api="https://api.usesequential.com"></script>

Keep both parts: the small inline script and the tag.

  • The inline script defines window.sequential straight away and queues calls until the SDK arrives. So sequential.capture(…) in a click handler or route effect works on a cold page, and does not throw if an ad blocker stops the SDK loading.
  • data-key (required) is the project’s Sequential ingest key.
  • data-api (required) is Sequential’s API URL. It is not worked out from the script’s own address, because the script comes from the CDN.
  • data-opt-out (optional) starts every visitor opted out, for sites that wait for consent. See below.
  • data-autocapture="false" (optional) turns off automatic page views, clicks, form submits, rage clicks and dead clicks. Calls you make still work.
  • data-release (optional) names your app’s build, for example data-release="2.14.1" or a commit SHA. Sequential puts it on every event as the release, and release verification uses it to tell sessions before a fix from sessions after it. It must be 1 to 64 printable ASCII characters. The SDK also refuses the placeholder words that an empty build variable can give. For any other value, the SDK sends events without a release and writes one error to the browser console. sequential.register({ $release: '2.14.1' }) also works, and it wins over the attribute.

Already using Sequential

Only two values ever change: the project key and the ingest host. Both are attributes on the script tag — there is no init call to edit, and no second tag for replay.

html
data-key="YOUR_SEQUENTIAL_INGEST_KEY"
data-api="https://api.usesequential.com"

Replacing a vendor SDK or the replay line

This tag replaces both, and running it alongside a vendor SDK is not supported. They are separate sources with separate keys, so every page view would be counted twice, under two identities. When moving an app over:

  1. Remove the vendor SDK (its loader or package and its init call) and any /rec/recorder.js/… session-replay line. The Sequential SDK starts the recorder itself.
  2. Change the vendor calls the app makes to the Sequential equivalents below. The method names are close on purpose: posthog.capture becomes sequential.capture.
  3. Call sequential.identify(userId) on load for signed-in users. The SDK starts with a new anonymous id, so without it every known user shows up as new.

Calling it from your code

  • sequential.capture(name, props) records an event. Names starting with $ are reserved for Sequential’s own events, and capture() throws on them, so pick a plain name (checkout_started, not $checkout).
  • sequential.identify(userId, props) marks the visitor as a known user. The previous anonymous id travels with it as $anon_distinct_id. Sequential does not yet merge a visitor’s events from before identify into the identified user.
  • sequential.register(props) and sequential.unregister(name) add or remove properties that ride on every later event. To put your app’s release on every event, add data-release to the tag (see above), or call sequential.register({ $release: '2.14.1' }) on each page load. Release verification uses it.
  • sequential.account(id, traits) sets the account, such as a company or a workspace, that the visitor acts for. The id is a string or a number of 1 to 200 characters. The SDK ignores any other id and writes an error to the console. After the call, every event carries the account id. The traits are flat text, number or true/false values: up to 50 names of up to 100 characters, and values of up to 1,000 characters. The SDK drops other traits. It sends the traits in an $account event only when the account or the traits change. Accounts lists the account.
  • A snippet that you copied before 9 October 2026 has no account in its stub. With that snippet, a call of sequential.account() before the script loads fails. Copy the snippet again from Setup, or call account() after the script loads.
  • sequential.reset() starts a new anonymous visitor and forgets the account. Call it on sign-out.
  • sequential.captureException(error, props) records an error. Uncaught errors are not captured automatically, so call it where your app handles errors.
  • sequential.flush() sends what is queued now and returns a promise.
  • If your project has a list of allowed origins and the website is not on it, Sequential refuses the events with 403 origin_not_allowed. The SDK then stops sending for the rest of that page load and writes one warning to the console: "[sequential] Events refused: this website is not on the project's allowed websites (origin_not_allowed). This page sends no more events." The next page load tries again.
  • If the project sends more than 20,000 events a minute, Sequential refuses the batch with 429. The SDK waits for the time that Sequential gives (1 to 300 seconds), and keeps the refused batch and new events, at most 100. Then it sends them again, once. See Limits.
  • sequential.optOut() and sequential.optIn(): see below.

There is no npm package yet, and no published TypeScript declarations. In a TypeScript app, read it through window with a narrow local type rather than adding a global declaration file.

Page views

The SDK records a $pageview when the page loads. In a single-page app, it records one more each time the page changes: after history.pushState, after history.replaceState, and after the browser’s back and forward buttons (popstate).

  • The page is the path and the query string. A different query, for example ?plan=team, is a different page.
  • The fragment is part of the page only for a hash route, one that starts with #/ or #!/. So #/a to #/b is a new page. Any other fragment, for example #features, is a place on the same page, and it records no page view.
  • The SDK compares the page only with the last page that it recorded. If the page did not change, for example when a router calls replaceState for its own state, the SDK records no page view. A return to an earlier page records a page view again.
  • From 9 October 2026, $current_url keeps a hash route, up to its first ?, & or a second #. The SDK redacts it with the rest of the address, and drops a route of more than 512 characters. It drops every other fragment. Before that date, $current_url had no fragment, so the page views of two hash routes had the same URL.
  • Each $pageview has $nav, which tells how the page was reached:
    • load: the first page view of a page load.
    • push: history.pushState, in every browser. With the Navigation API, also a link to a hash route or a change to location.hash.
    • replace: history.replaceState, in every browser, for example a redirect in your router. With the Navigation API, also a navigation of the replace type.
    • pop: with the Navigation API, the Back or Forward button, history.go, or a move back or forward across hash routes.
    • unknown: every page view from popstate in a browser without the Navigation API. The SDK does not guess.
  • The load page view also has $load_type: navigate, reload, back_forward, prerender or unknown.
  • When the browser shows a page again from its back/forward cache, the SDK records no page view.

From 9 October 2026: before this date, the SDK recorded a page view after every pushState, replaceState and popstate, also when the page did not change. Thus many single-page apps got duplicate page views. From this date, page-view counts on those sites go down. Funnels and flows with page-view steps change too: repeated steps on the same page go away, and the conversion between page steps can go up. The AI judge also sees fewer repeated page views. Sequential does not change older events, so a chart that spans this date shows a step down on it.

Rage clicks and dead clicks

The SDK also finds clicks that get no response, and sends them as $rageclick and $deadclick events. Sequential stores them as rage clicks and dead clicks, the same as these events from other SDKs, and Findings use them. data-autocapture="false" turns off both.

Rage click. A burst is a series of clicks near the first click (30 pixels or less), each less than 1 second after the click before it. The burst is a rage click when 3 of its clicks come within 1 second.

  • The SDK sends one event for each burst, 1 second after its last click. The event has the number of clicks ($click_count) and the time of the first click.
  • If the page responds during the burst, the SDK sends no event. A response is a change to the page, a new URL, a scroll, a text selection, or focus that moves to a different element. A change to the attributes of the clicked element is a response only when it changes a state attribute: aria-checked, aria-pressed, aria-expanded, aria-selected, open, checked or value. So a triple click that selects text is not a rage click, and a switch that changes its state when it is clicked is not a rage click. A switch that does not change is.
  • If the person leaves the page during a burst, the SDK sends the burst immediately.
  • These clicks are not counted: clicks in inputs that are not buttons (for example text, number, range and checkbox inputs), in select and textarea elements, and in editable areas. To exclude an element and everything in it, add data-sq-no-rage to it.
  • Clicks on a disabled button count.

Dead click. A dead click is a click on a link, a button, a submit input, an element with role="button" or an element with data-sq, after which nothing happens for 2.5 seconds: no change to the page, no new URL, no scroll, no text selection, and no focus that moves to a different element.

  • The event has the wait ($wait_ms, 2500) and the time of the click.
  • These clicks are not counted: links with download, links that open a new tab (target="_blank"), mailto:, tel: and sms: links, clicks on or in a label, inputs that are not buttons, and clicks while the tab is hidden.
  • A new click stops the wait for the click before it, and that click sends no event. If the person leaves the page during the wait, the SDK sends no dead click.
  • A click on a disabled button counts.

For both: the SDK counts only clicks with the main mouse button and no modifier key (Ctrl, Cmd, Shift or Alt). Both need MutationObserver in the browser. Without it, the SDK sends neither.

The Rage clicks signal of the default rubric also works with this SDK: the judge reads repeated clicks in the session’s events. The replay player also marks the rage clicks and dead clicks that it finds in the recording.

sequential.optOut() stops analytics and session replay, and is remembered in the browser (localStorage, key __sq_opt) across reloads. sequential.optIn() clears it. For a site that waits for consent, add data-opt-out to the tag: every visitor starts opted out, until your consent banner calls sequential.optIn(). The attribute is a default, not a stored choice. Because of the inline script, both calls work before the SDK has loaded.

What leaves the page

  • Page URLs, in the browser: before sending, the SDK drops the #fragment and any user:pass@ credentials, redacts values that look like JWTs, Bearer / Basic credentials, emails or card numbers, and caps the URL at 512 characters. Other query values, such as an opaque token=abc123, are sent as they are.
  • Page URLs, at Sequential: before anything is stored, every query value is replaced with [redacted] unless it is a marketing-attribution parameter (utm_*, gclid, fbclid and similar). The same rule applies to every source; see What Sequential keeps from page URLs. So do not treat URLs as a place where secrets are safe from Sequential: some reach its servers, and are only discarded there.
  • Autocapture records clicks on interactive elements and form submits, never what anyone types. Click text is redacted with the same rules as replay DevTools, which catch JWTs, Bearer / Basic credentials, emails and card numbers. A JWT written straight after a letter, digit, _ or - can slip through. Anything inside an element marked data-sq-mask contributes no text. data-sq-private on an element or an ancestor goes further: the click is still recorded, but with no text, no data-* values and no element id, and its selector has no ids. From 10 October 2026, the SDK also leaves out a click's text and data-* values by default when the element or one of its 3 nearest ancestors is account UI, or when the text names the signed-in person. Account UI has the word avatar, account, profile or user-menu in its class, id or data-testid, or account, profile or user in its aria-label. The signed-in person is an email, or a word of 3 or more letters of a name, that your app gave to identify(): the id, or traits such as email, name or first_name. The SDK keeps these words in memory only. If the selector names the person, it has no ids. To turn this off, add data-identity-text="off" to the script tag. Autocapture waits until the project’s configuration has loaded and stays off if it cannot load it, so nothing is captured before the redaction rules arrive.
  • In private browsing, or where the site blocks storage, the SDK cannot keep its ids across page loads. Each page view then counts as a new visitor, and those events carry $ephemeral_id: true.

What is supported

What Sequential does with Sequential SDK traffic
CapabilitySupportNotes
Custom eventsYessequential.capture(name, props); properties are kept. Names starting with $ are reserved for Sequential’s own events: capture() throws on them.
Page / screen viewsYes$pageview on load, and on SPA navigation (pushState, replaceState, popstate) only when the page changes. An in-page #anchor is not a new page; a #/ or #!/ hash route is.
Autocaptured clicks & formsYesClicks on interactive elements and form submits; never input values. Rage clicks and dead clicks too. Click text is redacted, not just truncated, and anything inside data-sq-mask contributes no text or data-* values. Autocapture waits for the project’s config and stays off if it cannot load it.
Identify (user id)Yessequential.identify(id). The previous anonymous id is sent as $anon_distinct_id, but events from before identify are not yet merged into the identified user.
User propertiesPartialregister() properties ride on every later event, but are not stored as user properties.
Groups / accountsYessequential.account(id, traits): the account id on every later event, and its traits in an $account event.
ErrorsPartialsequential.captureException(err) is stored as an error. Uncaught errors and unhandled rejections are not captured, so call captureException where you handle them.
Session replayYesThe same tag records sessions. The SDK starts Sequential’s recorder itself, with the replay key from the project’s config, so there is no second tag and no second key. Masking comes from the project’s recording settings.
DevTools: errors, console, network, performanceYesCaptured by the recorder the SDK starts, on the same terms as every other source, subject to the project’s DevTools settings.
Feature flags, surveys, experimentsNoNone, deliberately: flags, surveys and experiments need server-side products Sequential does not have, which is also why the SDK is a fraction of a vendor SDK’s size.
Server-side SDKsNoBrowser only. Server-side SDK user agents are rejected as bots (403).

Session replay is part of this tag. What is recorded and masked is set per project (Settings → Projects → your project → Session replay), and replay only runs if it is enabled there. See Session replay for those settings and DevTools.

Content-Security-Policy

Only if the site sets a Content-Security-Policy. Add these origins to the directives it already has; do not replace them.

text
script-src https://api.usesequential.com https://cdn.usesequential.com
connect-src https://api.usesequential.com

Check it works

  1. Load a page of the site in a normal browser (headless and automated browsers are rejected as bots) and open the network panel.
  2. Look for requests to the Sequential API URL that answer 2xx. A 401 means the key is wrong, or belongs to another source (every key works with one SDK only). A 403 means the request looked like a bot.
  3. In Sequential, Settings → Projects → Setup shows your source as "receiving" once its first event arrives. Until then, the Sequential SDK and session replay read "no events yet", and another source's line does not show. The page checks again every few seconds for 5 minutes after you open it. After that, reload it. The first event can take up to about a minute to appear.

More in Check your install.