문서
문서 둘러보기

Optional tracker modules

Load optional tracker modules for time on page and scroll depth, automatic outbound link, download, form and 404 events, Web Vitals and aggregate click maps.

Markdown으로 보기

Modules are small separate files the tracker loads on demand, from the same origin as the tracker (or data-api). Choose them with data-modules:

html
<script defer src="https://privatusstats.com/js/pa.js" data-site="pa_YOURSITEID"
        data-modules="engage,auto,vitals,clicks,ask"></script>
Module Default What it adds
engage On Time on page (visible time only) and scroll depth
auto Off Outbound links, file downloads, mailto:/tel: links, form submits and 404s as events. Loads pa.auto.js (about 1.4 KB)
vitals Off Web Vitals: LCP, INP, CLS, FCP, TTFB. Loads pa.vitals.js (about 1.5 KB)
clicks Off Aggregate click counts per element for click maps. Loads pa.clicks.js (about 1.3 KB)
ask Off Cookieless on-site surveys: a small card with your question. Loads pa.ask.js (about 7.5 KB)

The Default column is what the tracker loads when the script tag has no data-modules attribute. The snippet in Site settings → Tracking (and in the setup page and the API) lists all five, so every report has data from the start and your first survey works without touching the snippet again. Untick a module there to leave it out.

Warning: data-modules replaces the default. data-modules="auto" turns engage off. Write data-modules="engage,auto" to keep both.

engage: time on page and scroll depth#

Built into pa.js (no extra file). It measures:

  • Engaged time: only while the tab is visible. Switching tabs or minimizing pauses the clock.
  • Scroll depth: the deepest point reached, as a percentage of the scrollable height, stored in 10% bands.

It sends an engagement hit when the page is hidden or closed, and before the next SPA pageview, if at least one second of visible time has passed. It also sends one at the visitor's first input on the page (see the interaction flag). The server counts at most 4 hours of engaged time per hit. Engagement hits aren't pageviews or events: they don't count toward your usage.

Without engage, time on page, visit duration and the "under 10 seconds" part of bounce rate can't be measured.

auto: automatic events#

Loads pa.auto.js and tracks these events. They are custom events, so they count toward your usage. The module has no options: it sends all six or none.

Event name When Properties
Outbound Link A click on an http(s) link to another host (a subdomain counts as another host) domain (without www.), url (origin and path, no query)
File Download A click on a link whose path ends in a file extension from the list below, on any host file (file name), host
Email Link A click on a mailto: link to_domain (the part after @, never the address)
Phone Link A click on a tel: link none (the number isn't sent)
Form Submit Any form is submitted form: the form's id, else its name, else its action
404 When the page loads, its <title> contains "404" or "not found" (any case), or the page has <meta name="privatus-404"> path

File extensions counted as downloads: pdf, zip, gz, tgz, rar, 7z, dmg, exe, msi, pkg, deb, rpm, apk, ipa, csv, xls, xlsx, doc, docx, ppt, pptx, odt, ods, txt, rtf, epub, mobi, mp3, mp4, mov, avi, wav, webm, iso, json, xml, svg. A link to a file on another host is a File Download, not an Outbound Link.

Links and forms with a data-privatus-event attribute are skipped by auto, so you never count one click twice.

The 404 check runs once, when the module loads. It doesn't run again on SPA navigations.

For a reliable 404 event on pages whose title doesn't say "not found", add the meta tag to your 404 template:

html
<meta name="privatus-404" content="true">

Create goals of type Outbound click or File download to turn these into conversions.

vitals: Web Vitals#

Loads pa.vitals.js, which uses PerformanceObserver to measure:

Metric Unit
LCP Largest Contentful Paint milliseconds
INP Interaction to Next Paint (the slowest interaction) milliseconds
CLS Cumulative Layout Shift unitless, 3 decimals
FCP First Contentful Paint milliseconds
TTFB Time to First Byte milliseconds

Values are sent once per page load, one hit per metric, when the page is first hidden. In a single-page app that means once for the whole session on the page, not once per route. Browsers without PerformanceObserver send nothing, and a metric the browser doesn't support is left out.

Web Vitals hits are sampled on the server at the site's sample rate (Site settings → Tracking → Web Vitals sampling, 0 to 100%, 100% by default, where the Free plan allows up to 10%) and never count toward usage. Results are on the Performance page.

clicks: click maps#

Loads pa.clicks.js, which counts clicks per element for aggregate click maps. For each click it sends one name:

  • When the clicked element or one around it has a name, data-privatus-click="buy-pro", it sends [data-privatus-click="buy-pro"]. A name may have letters, digits, ., _, : and -, up to 100 characters. Any other value is not sent.
  • Otherwise a short CSS selector (up to 4 levels, e.g. nav.main > ul > li > a.pricing) of the nearest link, button, input, select, label, summary, [role=button] or element with data-privatus-click, or of the clicked element itself when none of those contains it. Each level is the tag name plus the id, or else up to two classes, and the selector stops at the first ancestor with an id. Ids and classes that look generated are left out: three or more digits in a row, or eight or more capital letters or digits in a row (the shape of a record id or a token). So are classes containing :.

The server cuts names at 512 characters. It never sends mouse movement, coordinates, the text of the element or anything typed. Clicks are throttled to one per 300 ms.

When you open a page from its click map (Show on the page), pa.js also loads pa.overlay.js, which draws the click map on the page. It is never loaded for visitors, and nothing is sent while it is open.

Click hits don't count toward your usage.

ask: on-site surveys#

Loads pa.ask.js, which shows a cookieless survey card on your own pages.

Once per page load it asks GET /api/surveys what to show, sending the site id, the page URL and the dnt and gpc flags when the browser sets them. The URL is the same minimized one a pageview reports: the canonical link when data-canonical is on, your data-mask replacements applied and the query string reduced to campaign parameters and your data-params allowlist. It asks nothing at all on a page data-exclude leaves out, or when the visitor has opted out. It doesn't ask again on SPA route changes.

We answer with at most one survey, and only what the card needs to draw itself: its public id, the trigger, the questions, the position, the thank-you message and the card's button labels. Your targeting rules stay on the server.

When the card is shown, when it is closed with something already answered, and when it is sent, the module posts to /api/survey with the survey's public id, which of the three happened and the answers given. Both requests use credentials: 'omit', so no cookie of yours is ever sent with them.

It stores nothing in the browser: no cookie, no localStorage, no sessionStorage and no id of any kind. Asking at most once a day is remembered on our side, under a daily hash that is never stored (see Surveys).

Styles are set through the CSSOM (element.style), which a Content Security Policy doesn't restrict, so no style-src exception is needed. The card follows prefers-color-scheme and prefers-reduced-motion.

What the request costs#

The snippet we give you includes ask, so a survey you create later shows up without a change to your pages. Take ask out of data-modules if you never want the request: a site without it makes no extra request at all.

When it is on, the one GET /api/surveys per page load is answered from Redis and makes no database query: the site's collecting surveys and their targeting rules are cached, and so is the one lookup a plan with a monthly response allowance needs. A typical answer is a few hundred bytes, or 11 bytes when there is nothing to show. The request is sent after the page's own work, because the tracker and its modules load with defer, so it never holds up rendering.

Pausing a survey, changing it or changing plan takes effect at once: the cache entry is rebuilt on the next request rather than waiting to expire.

Survey responses don't count toward your usage.