Connect your data

RUM script

Reference for the VitalSentinel RUM script - snippet, data attributes, consent mode, SPA support, JavaScript API, collected data and troubleshooting.

View as Markdown

The RUM script (rum.js) collects Core Web Vitals, JavaScript errors and user engagement from the real visitors of your site. It feeds RUM Monitoring. RUM means Real User Monitoring: measurement in the browsers of real visitors, not in a test lab.

For the install flow and the install check, see Tracking Scripts.

The RUM snippet

Add this tag to the <head> of every page:

<script
  src="https://rum.vitalsentinel.com/rum.js"
  data-key="YOUR_TRACKING_ID"
  async
></script>

The RUM script is small and loads with async, so it does not block your page. Copy the snippet with your real tracking ID from domain Settings > Tracking > Real User Monitoring (RUM) > Install snippet. The RUM script and the Web Analytics script use the same tracking ID.

The Install snippet has two forms: HTML ("Paste into your site's <head>.") and Google Tag Manager ("Paste into a Custom HTML tag in GTM.").

Data attributes

The RUM script reads these data-* attributes on its script tag:

AttributeDefaultWhat it does
data-keyrequiredThe tracking ID of your domain.
data-sample-rate1.0The share of visitors to measure, from 0.0 to 1.0. 0.5 measures half of the visitors.
data-engagementtrueTracks clicks, scroll depth, time on page and form interactions. Set false to stop it.
data-consentnoneSet granted when your consent banner has the visitor's acceptance. See Consent mode.
data-debugfalseWrites log messages to the browser console.
data-mask-textfalseRemoves the visible text of the elements recorded for Core Web Vitals attribution.
data-mask-selectorsfalseRemoves the CSS selectors recorded for LCP, CLS and INP attribution and for engagement events.
data-filter-query-paramsfalseRemoves query parameters from the tracked URLs.
data-track-non-productionfalseSends data from hosts that the script skips by default. See Non-production hosts.

The script reads data-consent first. If it is not set, the script reads the older attribute data-storage, so old snippets continue to work. data-storage takes none, session or persistent. With session, the script keeps only the visit ID, in session storage. Any other value means no consent. New snippets use data-consent.

RUM options in Settings > Tracking

The Real User Monitoring (RUM) card in domain Settings > Tracking builds the snippet for you. Open Options to change these settings:

OptionChoicesSnippet attribute
Consent modeNo consent (default) or Consent givendata-consent="granted" for Consent given
Sample rate100%, 75%, 50%, 25%, 10%, 5% or 1%data-sample-rate below 100%
Engagement tracking > Enable trackingOn (default) or offdata-engagement="false" when off
Debug mode > Enable console loggingOff (default) or ondata-debug="true" when on

The Options line shows what differs from the defaults, for example Consent given or 50% sample. VitalSentinel saves the first three options for everyone on the domain. Debug mode stays in your browser only ("Debug logging, for development only. Kept in this browser.").

A change to an option does not change the snippet that is live on your site. Copy the snippet again and put it on your site after each change.

SPA support

A single-page app (SPA) changes the page in the browser without a full page load. The RUM script finds these page changes on its own through the History API.

For a custom router, hash-based navigation or other state changes, start the page change by hand:

window.VitalSentinelRUM.startSoftNavigation();

The consent mode sets what the script can read from the browser and what it can keep on the visitor's device. The RUM script never sets a cookie.

In the No consent mode, the script saves nothing on the device: no cookies, no local storage, no session storage. It reads no device details except the window size, rounded to the nearest 50 pixels. It also checks a flag for headless automation and the Do Not Track and Global Privacy Control settings, so that it can obey them.

You still get Core Web Vitals, JavaScript errors, user engagement and the element behind each layout shift. You also get the country, browser, operating system and device type. The app says "No consent banner needed for storage."

If an earlier consent left IDs or the vs_cg key on the device, the script deletes them about 5 seconds after the page loads. It does not delete them if your banner grants consent first.

The Not collected in this mode line lists what you do not get:

  • Screen size - screen size and pixel ratio are missing from the per-page-view detail.
  • Device memory and processor - "You cannot tell whether a slow page is really just a slow device."
  • Connection speed - "A slow page and a weak network look the same."

Use the Consent given mode for visitors who accepted your consent banner. It adds screen size, device pixel ratio, device memory, CPU cores, touch support and connection speed. With these, you can tell a slow page from a slow device or a weak network.

The script also keeps a visit ID and a visitor ID in the browser's local storage, so page loads link into visits. It sets no cookie. The Web Analytics script uses the same keys:

KeyWhat it holdsHow long it stays
vs_sid, vs_sid_tsThe visit ID and its timeA new visit starts after 30 minutes without activity.
vs_vid, vs_vid_expThe visitor ID and its end date180 days from creation. Visits do not extend it.
vs_cgA note that your banner called grantConsent(). The script writes it only after that call.180 days from the latest grantConsent() call. revokeConsent() deletes it earlier.

Use Consent given only when your banner has the visitor's consent. The app says "Only use this once your banner has the visitor's consent."

To declare consent on the script tag, add data-consent="granted":

<script
  src="https://rum.vitalsentinel.com/rum.js"
  data-key="YOUR_TRACKING_ID"
  data-consent="granted"
  async
></script>

You can also change the mode at runtime from your consent banner. This needs no page reload. The section Using a consent banner? in the RUM options shows these calls:

// Visitor accepted
window.VitalSentinelRUM.grantConsent('persistent');

// Visitor rejected, or withdrew a previous acceptance
window.VitalSentinelRUM.revokeConsent();

The window.VitalSentinelRUM object exists only after rum.js has loaded, so make the calls after that. If the script is not ready yet, it keeps the calls in a queue. Add both calls to your banner: the first when a visitor accepts, the second when the visitor refuses or later changes their mind.

When a visitor accepts, the script uses the visit ID and the visitor ID that are already on the device. A withdrawal applies from that moment on. The script sends events that are already in its queue with the context that they had when it collected them. Then it deletes the visit ID and the visitor ID from the device.

Do Not Track and Global Privacy Control

The RUM script obeys a Do Not Track or Global Privacy Control signal from the browser. You do not need to block the tag for these visitors yourself. For these visitors:

  • The script reads no device details except the window size, whatever consent mode you declared.
  • User engagement tracking does not run.
  • The script keeps nothing on the device, and VitalSentinel makes no visit ID or visitor ID.
  • VitalSentinel keeps only the country. The region, the city and the time zone stay empty.
  • The script still measures Core Web Vitals and page views, so the traffic stays in your reports.

If the visitor accepts your consent banner, call grantConsent(). The visitor's own choice wins over the browser signal, so the script uses the mode from the call. A mode that you write in the snippet, with data-consent or data-storage, does not win over the signal. While the grant applies, each batch that the script sends carries cg: 1. Then the VitalSentinel servers also ignore the DNT and Sec-GPC headers for that visitor.

To remember the grant, the script keeps the key vs_cg in the same storage as the visit ID. The key lasts 180 days from the latest grantConsent() call. On a later page load, the script reads this key only if the snippet declares a consent mode. If the snippet keeps No consent, your banner must call grantConsent() again on each page load. revokeConsent() deletes the key, and the browser signal applies again.

User engagement tracking starts when the grant call comes. If the visitor withdraws consent, revokeConsent() stops it again.

What the RUM script collects

Core Web Vitals and loading metrics

The RUM script tracks all Core Web Vitals and the other loading metrics:

  • LCP (Largest Contentful Paint): loading performance
  • CLS (Cumulative Layout Shift): visual stability
  • INP (Interaction to Next Paint): response to interactions
  • FCP (First Contentful Paint): the first render
  • TTFB (Time to First Byte): server response time

It also records navigation timing: DNS lookup, TCP connection, request and response, DOM load and full page load.

User engagement

When engagement tracking is on (the default), the RUM script tracks:

  • Scroll depth
  • Time on page and active time
  • Page visibility changes
  • Clicks on interactive elements
  • Rage clicks: three or more clicks on the same element within two seconds
  • Form engagement: focus, changes, submissions and abandonment

JavaScript errors

  • JavaScript errors with stack traces
  • Unhandled promise rejections
  • Resources that fail to load, such as images, scripts and stylesheets

Device information

In every mode, the script collects the window size, rounded to the nearest 50 pixels. In every mode, VitalSentinel also finds these details on its servers, from the request headers that every browser sends:

  • Country, browser, operating system and device type
  • If the device is a phone or a tablet
  • If the visitor has data saver turned on

With Consent given only, the script adds screen size, device pixel ratio, device memory, CPU cores, connection type and speed, and touch support.

Privacy by default

  • No cookies in any mode. In the default mode, also no local storage and no session storage.
  • No tracking across sites.
  • In the default mode, the script makes a new random session ID on every page load. To link the page loads of one visit, VitalSentinel makes a short-lived ID on its servers. See Data and privacy.
  • The script removes record identifiers from page paths before VitalSentinel stores a URL.

A long number, a UUID, a hex string or a long random token becomes :id or :uuid. For example, /orders/44172/invoice becomes /orders/:id/invoice. The shape of the path stays, so these page views group as one page. Dates, short segments and word slugs stay as they are.

Privacy attributes

<script
  src="https://rum.vitalsentinel.com/rum.js"
  data-key="YOUR_TRACKING_ID"
  data-mask-text="true"
  data-mask-selectors="true"
  data-filter-query-params="true"
  async
></script>
  • data-mask-text removes the visible text recorded for the LCP, CLS and INP attribution elements. Engagement events carry no element text, so this attribute does not change them.
  • data-mask-selectors removes the CSS selectors recorded for LCP, CLS and INP attribution and for engagement events. Use it if your element IDs or class names contain record identifiers, such as #order-88213.
  • data-filter-query-params removes query parameters from the tracked URLs.

Tracking IDs and hostnames

VitalSentinel discards a RUM hit from a host that cannot be a public production site. A reload on a developer's localhost gives Core Web Vitals that no real visitor gets. It also uses your page-view allowance.

Unless the host is a domain that you added to VitalSentinel, VitalSentinel discards hits from:

  • localhost
  • Any bare IP address, private or public, IPv4 or IPv6
  • Hostnames on suffixes for testing and local development, such as .localhost, .local, .test, .internal, .invalid and .example

VitalSentinel keeps all other hosts, also a host that is not the domain of the tracking ID. A domain that you added is always accepted, also when it has one of the forms above.

Why the RUM rule is less strict than the Web Analytics rule

Web Analytics accepts only the exact host of the domain. It drops shop.example.com and staging.example.com under the tracking ID of example.com. RUM keeps them, for two reasons.

First, the data-track-non-production setting stays in the browser and never reaches VitalSentinel. A strict host rule on the server would delete the data of every site that uses it. Second, real traffic can arrive on other hosts. Examples are Google Translate readers on *.translate.goog, or a second site that shares one tracking ID.

For a subdomain with its own audience, add it as its own domain and use its tracking ID. This keeps the numbers of the two sites apart. See Add a Domain.

Non-production hosts

The RUM script also has its own, wider check in the browser. It sends nothing from localhost, loopback and private addresses, reserved test domains, and staging, dev and test subdomains. Thus, test visits do not mix with real visits.

If your production site uses such a hostname, set data-track-non-production="true":

<script
  src="https://rum.vitalsentinel.com/rum.js"
  data-key="YOUR_TRACKING_ID"
  data-track-non-production="true"
  async
></script>

This attribute stays in the browser, so it removes only the browser check. It works for a name like staging.example.com, which the server keeps. It cannot make localhost or a bare IP address work, because the server discards them. The only exception is a host that you added as its own domain.

JavaScript API

The RUM script has a JavaScript API on window.VitalSentinelRUM. The shorter name window.VSRUM is the same object.

Track a custom event

window.VitalSentinelRUM.trackCustomEvent('purchase', {
  productId: '12345',
  value: 99.99,
  currency: 'USD'
});

Mark and measure points in time

window.VitalSentinelRUM.mark('hero-start');
window.VitalSentinelRUM.mark('hero-loaded');

// Measure the time between two marks
window.VitalSentinelRUM.measure('hero-time', { start: 'hero-start', end: 'hero-loaded' });

Add data to all events

window.VitalSentinelRUM.addData('plan', 'premium');

The script adds this key and value to all events after the call.

Listen to RUM events

window.VitalSentinelRUM.on('mark', (eventType, data) => {
  console.log('Mark created:', data.name, data.startTime);
});

window.VitalSentinelRUM.on('measure', (eventType, data) => {
  console.log('Measure recorded:', data.name, data.duration);
});

window.VitalSentinelRUM.on('soft_navigation', (eventType, data) => {
  console.log('SPA navigation:', data);
});

Send queued events now

window.VitalSentinelRUM.forceFlush();

Queue commands

The script makes its own command queue when it loads. Use cmd() to queue a command:

window.VitalSentinelRUM.cmd(['mark', 'app-start']);

// trackCustomEvent cannot go in the queue. Call it directly.
window.VitalSentinelRUM.trackCustomEvent('page_intent', { category: 'pricing' });

Troubleshooting

Some problems affect both scripts: a Content Security Policy, caching plugins, Cloudflare Rocket Loader, consent tools, ad blockers and Google Tag Manager. For these, see Tracking Scripts.

Why does the RUM script not load?

If the browser console shows an error for rum.js, read it first. If an ad blocker runs, turn it off for the test. Make sure that the data-key value is the tracking ID of this domain.

If your site has a Content Security Policy, the RUM script needs two hosts. Add https://rum.vitalsentinel.com to script-src, so that rum.js can load. Add https://app.vitalsentinel.com to connect-src, so that the script can send its data. If rum.js loads but no request goes to app.vitalsentinel.com, the connect-src directive is usually the cause.

Why does RUM Monitoring show no data?

  • If you test from localhost, a private address or a staging, dev or test subdomain, the script sends nothing by default.
  • If data-sample-rate is very low, few visits are measured.
  • If the page host is a bare IP address, the server discards the hit.
  • If you open the page from a file on your computer (file://), the script does nothing.
  • If the script tag has no data-key, the script does not start.

Numbers appear within minutes of the first real visit.

Why does the console say "Non-production host, tracking disabled"?

With debug mode on, the RUM script writes [VitalSentinel RUM] Non-production host, tracking disabled. when it skips the host of the page. The RUM script skips these hosts by default:

  • localhost, loopback and private addresses
  • Reserved test domains, such as .test, .local, .localhost, .internal, .example and .invalid
  • Hosts with three or more labels that start with staging, stage, dev, development, test, testing, qa, uat, sandbox, preview or local

Thus, dev.example.com is skipped, but dev.to is not. If your real site uses such a host, add data-track-non-production="true". See Non-production hosts.

Why does the console say "Session not sampled"?

With debug mode on, the RUM script writes [VitalSentinel RUM] Session not sampled when the Sample rate leaves out this page view. The script decides again on each page load. At a sample rate of 10%, about nine page loads in ten send nothing. For tests, set the Sample rate to 100%, copy the snippet again and replace it on your site.

Why do my automated tests send no RUM data?

The RUM script does nothing in a browser that automation controls, such as Selenium, Puppeteer or Playwright. Such a browser tells the page that a program controls it. VitalSentinel also drops batches from known crawlers on its servers. To test the script, open your site in a normal browser.

Why does RUM Monitoring show no engagement for some visitors?

The RUM script does not track engagement for visitors who send Do Not Track or Global Privacy Control. It still measures their Core Web Vitals and JavaScript errors. If data-engagement="false" is on the tag, or Engagement tracking is off in the RUM options, the script tracks no engagement for any visitor.

Why do some page addresses show ":id" or ":uuid"?

The RUM script replaces record identifiers in page paths before VitalSentinel stores a URL. For example, /orders/44172/invoice becomes /orders/:id/invoice. Thus, all order pages group as one page. This is expected. See Privacy by default.

Why does the RUM script not start a second time?

The RUM script starts only once on a page. If the page has two copies of the snippet, the second copy does nothing. The console can show [VitalSentinel] Already initialized, ignoring duplicate init call. Remove the extra copy, so that the right tracking ID measures the page.

How do I see what the RUM script does?

Turn on debug mode. Add data-debug="true" to the script tag, or tick Enable console logging in the RUM options and copy the snippet again. Then open the browser console and look for messages that start with [VitalSentinel RUM].

<script
  src="https://rum.vitalsentinel.com/rum.js"
  data-key="YOUR_TRACKING_ID"
  data-debug="true"
  async
></script>

Remove debug mode when you are done. It is for development only.

On this page

VitalSentinel

Catch issues before they cost you

Track SEO, performance, and uptime in one place and get alerted the moment something breaks – hours before it hits your traffic.

  • Free plan for 1 domain
  • Set up in minutes
  • No credit card required