Docs·Usage

JavaScript SDK

The tracking script puts one object on your page, window.Spreelytics. This page is the full reference for it: how to load it, every method with its arguments and return value, every option of init(), and the requests it sends.

If you only need the script on your site, Tracking script is the shorter read.

Loading the script

The standard snippet

<script src="https://cdn.spreelytics.com/cdn/sdk/spreelytics-loader.js"></script>
<script>
  Spreelytics.init({ client_key: "YOUR_CLIENT_KEY" });
</script>

The first tag creates window.Spreelytics straight away, as a small stand-in that queues your calls. init() makes it fetch the main script from https://cdn.spreelytics.com. When the main script arrives, it takes the stand-in's place and runs the queued calls in the order you made them.

So you can call any method below as soon as the snippet has run, with two exceptions: trackLink() only exists once the main script has loaded, and getVisitorId() and getSessionId() return null until then.

The queue form

To load the loader without holding up the page, create the queue yourself and load the loader with async:

<script>
  window.Spreelytics = window.Spreelytics || [];
  window.Spreelytics.push(["init", { client_key: "YOUR_CLIENT_KEY" }]);
</script>
<script async src="https://cdn.spreelytics.com/cdn/sdk/spreelytics-loader.js"></script>

Until the loader has run, window.Spreelytics is a plain array, so only push() works on it, and a call such as Spreelytics.track(...) would throw. Use push() for every call: it works before and after the script loads. Always go through window.Spreelytics rather than keeping your own reference to it, because the loader replaces the array with its own object. Keep the init command first, because commands in front of it are dropped.

Loading from code

When your framework only lets you run code, or you want to load the script later, for example after a visitor accepts cookies, add the loader from JavaScript. This is how the Spreelytics app loads it for itself:

function loadSpreelytics() {
  const script = document.createElement("script");
  script.src = "https://cdn.spreelytics.com/cdn/sdk/spreelytics-loader.js";
  script.async = true;
  script.onload = () => {
    window.Spreelytics.init({ client_key: "YOUR_CLIENT_KEY" });
  };
  document.head.appendChild(script);
}

Run it from code that only runs in the browser: in a server-rendered app, that is a client-only plugin or component. Until onload fires, window.Spreelytics does not exist, so calls made before then fail unless you guard them, as in window.Spreelytics?.track("signup_completed"), and guarded calls are lost. To keep early calls, combine this with the queue form: create the array and push the init command before you append the script.

The later the script starts, the more short visits end before it can record them, so load it as early as your page allows.

Methods

init(options)

Spreelytics.init(options: object): void

Starts the script. client_key is required; the other options are listed under Init options. Returns nothing.

  • Call it once, before anything else. A second call is ignored, so options cannot be changed once the script has started.
  • Without a client_key, nothing is loaded and nothing is recorded.
  • Calls made before init() are dropped.
  • Before the first pageview, the script waits for the audience's cookieless setting, for 2.5 seconds at most, then records the pageview of the current page.
Spreelytics.init({
  client_key: "YOUR_CLIENT_KEY",
  track_links: "outbound",
});

page(url?)

Spreelytics.page(url?: string): void

Records a pageview. You rarely need it, because the first page and every route change are recorded by themselves. Returns nothing.

  • With no argument, it records the current address.
  • With an argument, it takes a full URL or a path, read against the current page the way a link's href is: /pricing on https://www.example.com/blog records https://www.example.com/pricing. A value that is not a valid address is ignored.
  • It does nothing when the address points at the page that was counted last. A new query string or an #anchor is not a new page; a new #/ route is.
  • If 30 minutes have passed since the last activity, it starts a new visit first.
  • The title it records is the document's title at that moment.

One use is a form whose steps do not change the address:

// After the visitor moves on to the second step
Spreelytics.page("/signup/step-2");

track(event, properties?, attributes?)

Spreelytics.track(event: string, properties?: object, attributes?: object): void

Sends a custom event. Returns nothing: the request goes out in the background.

ArgumentWhat it is
eventThe event name. Names are case sensitive, so signup_completed and Signup Completed are two different events.
propertiesDetails about this occurrence. Values can be text, numbers or true and false. Nested objects are flattened ({ cart: { total: 49 } } becomes cart.total), objects in an array become keys such as items[].name, a plain array is stored as one value per item, and null values are dropped.
attributesDetails about the person, with the same keys as identify(). They are applied only when the visitor has already been identified, and ignored otherwise.
Spreelytics.track("plan_purchased", { plan: "growth", seats: 12 });
  • It does nothing until the script has started a visit, which is also why nothing is sent from an automated browser.
  • An event counts as an interaction, so the visit is no longer a bounce.
  • A request under 16 KB is sent in a way that survives the page closing, so an event fired right before a redirect still arrives. A larger one can be cut off when the page changes.
  • Ten property names are kept for payment data: revenue.amount, revenue.currency, revenueReportingAmount, reportingCurrency, transactionId, refunded, renewal, isFreeTrial, customerName and customerId. Sent from the browser, they are stored but left out of the property lists in the app, and they add nothing to revenue. Name your own ids something else, such as account_id. Payments come from your payment provider or your server (see Revenue).

Track events covers naming events and choosing properties.

identify(email, attributes?)

Spreelytics.identify(email: string, attributes?: object): Promise<void>

Ties the current visitor to a person by email address. Their earlier anonymous visits join the person, and once the same person is identified on another device, both devices' activity lands on one profile. See Identify users.

  • Email only. The value has to look like an address: some text, an @, and a domain with a dot. Anything else is skipped without an error. With debug: true, the console says Identify skipped: invalid email format.
  • Send it in lower case. The check that finds an existing person compares the address exactly as you send it, while new people are saved in lower case, so Ada@Example.com can end up as a second profile next to ada@example.com. Trim and lower-case the address first.
  • Attributes. These keys are saved on the person, and their values must be text: first_name, last_name, company, phone, website, lang, address, address_line2, city, state, zip and country. Other keys are ignored.
  • Return value. A promise that resolves once the request has been answered, or at once when the call is skipped. It never rejects. Called while only the loader is on the page, the call is queued and returns undefined instead.
  • The request is sent in a way that survives the page closing, so calling it right before the redirect that follows a login is fine.
await Spreelytics.identify(user.email.trim().toLowerCase(), {
  first_name: user.firstName,
  company: user.companyName,
});

trackLink(element, properties?)

Spreelytics.trackLink(element: HTMLElement, properties?: object): void

Watches one element and sends a Link Click event each time it is clicked. The event carries the link's href, text and id, as described in Autocapture, plus your properties. When a name clashes, your value wins. Returns nothing.

  • It only exists once the main script has loaded. Called earlier, it throws Spreelytics.trackLink is not a function. The queued form works at any time:
Spreelytics.push(["trackLink", document.querySelector("#pricing-cta"), { position: "hero" }]);
  • Each element is watched once. A second call on the same element is ignored, along with its properties.
  • It works whether or not track_links is on. If track_links also covers the link, each click is recorded twice.
  • It is meant for links. On an element without an href, the href property holds the current page's address.
  • Link clicks count as an interaction, and do not count toward your allowance.

getVisitorId()

Spreelytics.getVisitorId(): string | null

Returns the anonymous id of this browser, which starts with vid_, or null.

  • It returns null until the script has started, which takes a moment after init(). On a cookieless audience the id is created by Spreelytics and arrives with the answer to the heartbeat request (see Requests the script sends), so it stays null until then, and a callback passed to push() can still see null.
  • Its main use is crediting payments to visits: pass it into your checkout as the metadata field spreelytics_visitor_id (see Revenue). Read it when the visitor checks out, not when the page loads.

getSessionId()

Spreelytics.getSessionId(): string | null

Returns the id of the current visit, which starts with sess_, or null before a visit exists.

  • Pass it next to the visitor id as spreelytics_session_id to tie a payment to the exact visit it came from.
  • On a cookieless audience, Spreelytics assigns visit ids itself and this value does not match them, so send only the visitor id there.

reset()

Spreelytics.reset(): Promise<void>

Call it when someone logs out, so the next person on the same browser does not carry on as them.

  • In the default mode it gives the browser a new visitor id (a new cookie), clears the current visit, starts a new one and straight away records a pageview of the current page for the new visitor. Call it right before you send the visitor away from the page.
  • On a cookieless audience there is no stored id to replace. Spreelytics keeps recognising the browser as the same visitor for the rest of the day, so reset() cannot separate two people who share a computer.
  • It returns a promise that resolves once the reset is done. Before init() it does nothing, and while only the loader is on the page it is queued and returns undefined.
async function logOut() {
  await Spreelytics.reset();
  window.location.assign("/login");
}

allowCookies()

Spreelytics.allowCookies(): void

For a cookie banner. Call it when a visitor whose page was started with cookieless: true clicks Accept (see cookieless). Returns nothing.

  • The visitor id Spreelytics has been counting them under is saved in the spreelytics_vid cookie. The next page they load, started without cookieless, carries on as the same visitor, so what they did before accepting stays theirs.
  • The rest of the current page is still counted without cookies, so the visit in progress is not split. On a site where every click loads a new page, the next page starts a new visit for the same visitor.
  • If they accept in the first moment after the page loads, before Spreelytics has sent back their id, the cookie is saved as soon as the id arrives.
  • On a page that already uses the cookie it does nothing. While only the loader is on the page it is queued and runs once the main script is ready.
acceptButton.addEventListener("click", () => {
  saveBannerAnswer("accepted"); // your banner's own record of the choice
  Spreelytics.allowCookies();
});

startReplay() and stopReplay()

Spreelytics.startReplay(): void
Spreelytics.stopReplay(): void

Start and stop session replay at moments of your own, such as sign-in and sign-out. Both return nothing. They matter only on an audience with session replay on.

By default a recorded visit is recorded from the moment the page loads. With session_replay: { manual_start: true } (see session_replay), nothing is recorded until startReplay():

Spreelytics.init({ client_key: "YOUR_CLIENT_KEY", session_replay: { manual_start: true } });

// After sign-in:
Spreelytics.startReplay();

// On sign-out, or before a part of the site that should not be recorded:
Spreelytics.stopReplay();
  • Each call lasts for the rest of the visit. After startReplay(), later pages keep recording without another call. After stopReplay(), nothing is recorded until startReplay(), and what was recorded before the stop is kept.
  • A new visit, after 30 idle minutes or reset(), goes back to manual_start. It is fine to call startReplay() on every page a signed-in user sees.
  • Neither call overrides the audience: nothing is recorded with session replay off, for a visitor counted without cookies, or past your plan's monthly replays.
  • Both can be called before the script has loaded; the loader queues them.

increment(), decrement() and multiply()

Spreelytics.increment(key: string, amount: number): void
Spreelytics.decrement(key: string, amount: number): void
Spreelytics.multiply(key: string, amount: number): void

These change a number stored on an identified person. They only act on a number field that already exists for the people in your workspace, and Spreelytics has no screen for creating one, so on a Spreelytics audience these calls have no effect.

push(command)

Spreelytics.push(command: unknown[] | (() => void)): void

Calls any of the methods above by name, and works at every stage: before the loader has run (in the queue form), while the main script is loading, and after. Returns nothing.

CommandDoes the same as
["init", options]init(options)
["page", url]page(url)
["track", event, properties, attributes]track(event, properties, attributes)
["identify", email, attributes]identify(email, attributes)
["trackLink", element, properties]trackLink(element, properties)
["reset"]reset()
["allowCookies"]allowCookies()
A functionRuns the function once the script is ready
Spreelytics.push(["track", "newsletter_signup", { form: "footer" }]);

Spreelytics.push(function () {
  console.log("Spreelytics is ready");
});

An unknown command name logs the warning [Spreelytics] Unknown command: and does nothing else.

Init options

OptionTypeDefaultWhat it does
client_keystringnone, requiredThe audience's client key, from Settings → Tracking script or Settings → API keys.
track_linksfalse, "outbound" or truefalseRecords link clicks as Link Click events: "outbound" for links to other hostnames, true for every link.
track_data_attributesbooleantrueRecords clicks on elements tagged with data-spree-track as custom events.
cookie_domainstring or falseworked out by the scriptThe domain the visitor cookie is set on. See cookie_domain.
redact_urlfunctionnoneRewrites page addresses before they are sent. See redact_url.
api_hoststringhttps://sdk.spreelytics.com/1.0The base address of every request the script sends, for a proxy on your own domain. Trailing slashes are removed. The script files still load from cdn.spreelytics.com. See Proxy guide.
cookielessbooleanthe audience's settingtrue counts this visitor without cookies, for a visitor who turned down your cookie banner or has not answered it. See cookieless.
session_replayobjectnoneHow session replay records, on an audience that has it on. See session_replay.
debugbooleanfalseLogs each step to the browser console, in lines starting [Spreelytics] and [Spreelytics Tracker].

Attributes on the <script> tag are not read: every option goes through init(). There are no options to turn off pageviews, scroll depth or the end-of-visit report, to change the 30-minute visit timeout or to sample traffic. The script does not read Do Not Track. For a cookie banner, either count visitors without cookies until they accept (see cookieless), or load the script only once the visitor has agreed (see Loading from code).

By default the visitor cookie goes on the widest domain the browser accepts for the page. On www.example.com that is example.com, so app.example.com and blog.example.com share the same visitor. Browsers refuse public suffixes such as co.uk, so a page on shop.example.co.uk gets example.co.uk.

  • A string sets the domain yourself. It has to be the page's hostname or a parent of it; otherwise the console shows a warning and the cookie stays on the exact hostname.
  • false keeps the cookie on the exact hostname, so each subdomain counts its visitors separately.
  • IP addresses, localhost and hostnames without a dot always get a cookie on the exact hostname.
  • It has no effect on a cookieless audience, where nothing is stored. There, the same person is recognised across your subdomains anyway, because Spreelytics works the visitor out from the request.

A shared visitor does not mean a shared visit: in the default mode each subdomain keeps its own visit (see Autocapture).

redact_url

A function that takes an address and returns the one to send. It runs on the page address, the referrer, the previous page, the exit page and the page path sent with scroll depth. It always receives a full URL, even for the scroll path, so new URL(url) works inside it.

Spreelytics.init({
  client_key: "YOUR_CLIENT_KEY",
  redact_url: (url) => url.replace(/\/invoice\/\d+/, "/invoice/:id"),
});
  • Before your function runs, the script already replaces any query-string value that looks like an email address with [redacted]. That step cannot be turned off.
  • If your function throws, or returns something other than text, the address goes out with only that first step applied.
  • It does not touch the href of link clicks, page titles or event properties.

cookieless

Left out, the script asks Spreelytics for the audience's setting on every page load and follows the Cookieless tracking switch. That is what most sites want.

Set it to true for one visitor at a time, when your cookie banner says so: they turned it down, or have not answered it yet. That visitor is counted the way a cookieless audience counts everyone. Nothing is stored in their browser, and Spreelytics works out who they are from each request. Everyone else on the audience keeps the cookie.

Spreelytics.init({
  client_key: "YOUR_CLIENT_KEY",
  cookieless: !visitorAcceptedCookies(), // your banner's own check
});

When the visitor clicks Accept on that page, call allowCookies(). From the next page on, start the script without cookieless. Cookieless tracking explains how these visitors are counted and what that costs.

false puts the cookie and browser storage back on a cookieless audience, even though Spreelytics then ignores them. It is only there for pages that load the main script without the loader.

session_replay

Whether an audience records visits is set in the app, under Settings → Tracking script, so your code turns nothing on. These options change how it records. Each is optional.

Spreelytics.init({
  client_key: "YOUR_CLIENT_KEY",
  session_replay: {
    mask_text_selectors: [".user-name", "#account-email"],
    block_selector: ".ads",
  },
});
OptionDefaultWhat it does
manual_startfalseRecords nothing until the page calls startReplay().
mask_all_inputstrueMasks what is typed into every form field. Password and email fields stay masked either way.
mask_text_selectors[]CSS selectors whose text is masked.
mask_text_class"rr-mask"A class whose elements' text is masked.
block_class"rr-block"A class whose elements are left out, shown as an empty box.
block_selectornoneA CSS selector for elements to leave out.
ignore_class"rr-ignore"A class for fields whose typing is not recorded at all.
ignore_selectornoneA CSS selector for fields whose typing is not recorded at all.
collect_fontsfalseRecords your web fonts, so the replay draws in them. Recordings get much bigger.
slim_dom_options"all"Leaves scripts, comments and head metadata out of recordings. false keeps them.

Whatever you set, three rules stay on: data-spree-mask masks an element's text, data-spree-block leaves an element out, and email addresses in page text are masked. A selector the browser cannot read is skipped rather than breaking the recording.

Requests the script sends

Every request goes to https://sdk.spreelytics.com/1.0, or to your api_host, followed by the path below.

RequestWhen
GET /track/configOnce per page load, before anything else. Asks whether the audience is cookieless and whether it records session replays; the answer can be reused for up to a minute.
POST /track/heartbeatOnce when the script starts, and after reset(). It drives the install status in Settings → Tracking script.
POST /track/pageFor every pageview.
GET /track/statsTwo seconds after each pageview. Reads back the visitor's page and visit counts.
POST /track/eventFor custom events, tagged clicks, link clicks and scroll depth.
POST /track/session-endEach time the tab is hidden, and when the page is closed.
POST /track/pingWhen a hidden tab becomes visible again.
POST /track/identifyFor identify().
PUT /track/attributeFor increment(), decrement() and multiply().
POST /track/replayWhile a visit is being recorded: a piece of the recording every 5 seconds, and when the page is hidden. Only on an audience with session replay on.

The POST requests carry a form-encoded body, a clientKey field and a data field holding JSON, which keeps them simple enough that browsers send no CORS preflight before them. A visitor counted without cookies also sends a cookieless field set to 1. POST /track/replay is the exception: it sends the recording gzip-compressed as plain text, with the key, the visit and the visitor in its address, and answers whether to keep recording. GET /track/stats and PUT /track/attribute are preceded by an OPTIONS preflight. Spreelytics answers with an empty 200, except for the config, heartbeat and stats requests, which return a little JSON, and processes what it accepted in the background.

What the script stores

In the default mode the script keeps a visitor cookie, spreelytics_vid, for a year from the last visit, plus spreelytics_visitor in local storage and spreelytics_session_data in session storage. On a cookieless audience, and for a visitor started with cookieless: true, it stores nothing until allowCookies(). Cookieless tracking lists every item and what it holds.

With session replay on, a recorded visit also loads the recorder, spreelytics-replay.js, from the same place as the script, and keeps spreelytics_replay in session storage once it has sent its first piece of recording.

See your own numbers today.

Free up to 100k events a month · no credit card

Contact

Tell us what you need.

A real person reads these. We usually reply within one working day.

Or email us directly at contact@spreelytics.com.