PilotFoxDocs

Tracking overview

How the PilotFox tracking script works, what it records and the global object it defines.

The tracking script, s.js, is one small classic script (about 9 KB compressed) that you add to every page. It sets no cookies.

<script defer src="https://pilotfox.app/api/s.js" data-site="SITE_ID"></script>

How it works

  1. It loads the site's settings from GET /api/c/SITE_ID, without credentials. The answer says which of the options below are on, whether session replay runs, and whether to respect Global Privacy Control. Browsers cache it for 60 seconds, so a change in Site settings reaches visitors within a minute. If it cannot be loaded (blocked, an error, or no answer within 10 seconds where the browser supports a timeout), the script falls back to safe defaults: the initial pageview, single-page app navigation and query strings, nothing else.
  2. It sends each pageview and event as one request, POST /api/e, with navigator.sendBeacon (or, where that is missing or refuses the request, fetch with keepalive and no credentials). The body is JSON sent as text/plain, so the browser sends no CORS preflight, and the server reads no cookie. identify and setTraits post to /api/i the same way.
  3. The server does the rest: it derives the visitor and the session, looks up location and device, applies your privacy settings and exclusions, filters bots and stores the event. See Concepts.

Everything goes to the directory the script was loaded from. Loaded from https://pilotfox.app/api/s.js, it talks to https://pilotfox.app/api/...; loaded through your own domain as /_pf/s.js, it talks to /_pf/....

RequestWhat it does
GET /api/s.jsThe script. Cached for an hour.
GET /api/c/{publicId}The site's tracker settings.
POST /api/eOne pageview or event.
POST /api/iIdentify a user, or set their traits.
GET /api/r.jsThe session replay recorder, loaded only on recorded tabs.
POST /api/rec/{publicId}A chunk of a session recording.

/api/e and /api/i answer 204 whether an event was stored or dropped (by an exclusion or the bot filter, for example): they never say why. They answer 400 for a malformed body, 404 for an unknown site, 413 for a body that is too large and 429 when a visitor's address sends too many requests.

What it records

These options are switches in Site settings → Tracking code. Browsers pick up a change within a minute.

OptionDefaultWhat it records
Initial pageviewOnA pageview when the script loads.
Single-page app navigationOnA pageview on each History API route change (see Single-page apps).
Hash routingOff#/path and #!/path changes as pageviews of that path.
Query stringsOnThe page's query string with each pageview. What is stored of it is a privacy setting.
Outbound linksOnClicks on http and https links to another host name than the page's (another subdomain of your site counts), with the link's address and text (up to 200 characters).
Button clicksOffClicks on buttons, submit and button inputs and role="button" elements (or up to three levels inside one), with their tag name, text (up to 100 characters) and id. One per element per second.
Form submissionsOffThe form's id, name, action path, method and number of fields, never the values.
Copy eventsOffThat text was copied, from which kind of element and how many characters, never the text.
Input changesOffThe name (or id) and type of a changed field, never the value. Password and hidden fields are left out.
JavaScript errorsOffUncaught errors and unhandled promise rejections from scripts of your own origin (a rejection whose reason is not an Error is always sent), at most 25 per page load, the same error once a minute.
Web vitalsOffLCP, CLS, INP, FCP and TTFB, sent once per page view.
Payment links and return pagesOnA checkout event with a fresh reference when a link to a Stripe, Dodo Payments or Polar payment page is clicked (the reference is added to the link), and with the payment's id on the page the provider sends the buyer back to. See Revenue.

Session replay is a separate setting (Site settings → Session replay); see Session replay.

The global object

The script defines window.pilotfox (or the name in the tag's data-namespace):

MethodWhat it does
pageview(url?)Sends a pageview of the current address, or of url. See Single-page apps.
event(name, props?)Sends a custom event. See Events and identify.
identify(userId, traits?)Links this browser to your user id, with optional traits.
setTraits(traits)Updates the identified user's traits.
reset()Forgets the user id (on sign-out).
getVisitor()Returns { uid }: the stored user id, or null.
optOut(), optIn()Stops tracking in this browser, or resumes it. See Opt-out.
startReplay(), stopReplay()Records this tab, or stops recording it. See Session replay.
loadedtrue once the script has run.

Calls made before the site's settings have arrived wait and run in order once they have (with a cap on waiting events). Call through window.pilotfox?. so a page whose script was blocked keeps working:

window.pilotfox?.event("signup", { plan: "pro" });

Calls made before the script itself has loaded can be queued; see Calls before the script has loaded.

On this page