Install

One tag, and everything worth knowing about it.

Pasting the tag takes ten seconds. This page is for the hours afterwards: what leaves your site, what never does, and what on your own page can make the numbers wrong.

The tag

<script defer
  src="https://panel.raicode.tech/v1/p.js"
  data-panel-key="pk_live_…"></script>

Swap the key for your own, from the dashboard. There is no second step, no consent library to wire in, and nothing to add to a cookie policy.

The defer matters: the tag runs once your document has parsed, so it never enters your first render.

Payload

What the tag sends

One small object per page view and per named event, posted as text/plain so that navigator.sendBeacon can still send it from a page that is closing. The keys are one letter because this object travels on your visitors' mobile connection, not on ours.

What it never sends

  • No cookie, no localStorage, no sessionStorage, no IndexedDB.

    Nothing at all is written to the visitor's device — which is exactly what removes the consent banner from your site. The build script greps the shipped bytes for all four APIs and fails the build if any of them appears.

  • No query string and no fragment, except five names.

    ?token=…, ?email=… and #… are precisely where a password-reset token or somebody's address ends up in an analytics tool, and there is no syntax that tells a safe parameter from a dangerous one — so the query string is dropped by shape. The exception is utm_source, utm_medium, utm_campaign, utm_term and utm_content, read by name. A name is not a shape: a URL carrying a reset token and a campaign yields the campaign and not one byte of the token. There is no gclid and no fbclid — a per-click identifier is the one thing this product refuses everywhere — and data-panel-utm="false" turns even the five off.

  • No page title.

    "Order #4471 — Jean Dupont" is a title bar, and it is the most common accidental leak of a customer's customer's name.

  • No full referrer.

    The host answers "where did this traffic come from"; the rest is somebody else's business.

  • No IP address.

    Your visitors' addresses are used server-side to derive a hash that rotates every night and to resolve a country, then discarded. They are written to no table and to no log line.

  • No screen size.

    The viewport is sorted into one of three classes in the browser itself, so the number never travels.

  • No identifier that outlives the day.

    There is nothing this script could store to recognise you tomorrow, because it stores nothing.

Identifying segments are erased from the path before it is sent: /orders/3f2504e0-… becomes /orders/:id, and bare numbers, e-mail addresses and token-shaped strings get the same treatment. The rule is shared with the server, which applies it a second time on arrival. It over-redacts deliberately — /products/iphone-15-pro-max becomes /products/:id, which costs a little reporting detail, where the other direction would cost somebody their privacy.

Options

The attributes

Only data-panel-key is required.

The JavaScript API

panel('signup', { plan: 'pro', seats: 3 });
panel.pageview();
panel.pageview('/cart');

Properties must be strings, numbers or booleans. Nested objects and arrays are dropped rather than flattened: flattening is how a whole user record ends up in four columns because somebody passed the object they already had. Anything that looks like an e-mail address inside a value is replaced before it leaves the page. Names starting with $ are reserved.

If your code calls before the script loads

<script>
window.panel=window.panel||function(){
  (panel.q=panel.q||[]).push(arguments)
}
</script>

The tag carries defer, so it runs after your document parses. If your own code calls panel(…) before that, add this stub above your other scripts: anything queued is sent in order once the script loads. It is deliberately not in the pasted snippet — the install is one line, and a second line most people do not need is a second thing to get wrong.

Diagnosis

The other scripts on your page.

Almost every "my numbers are wrong" report about a tracking script turns out to be about something else on the page.

So Panel looks, once per page load, and tells you. Two fields only, and they describe your page and never your visitor: the hosts of the other third-party scripts, and a flag if somebody had already wrapped history.pushState before us. These are facts you would read yourself in your own site's source, identical for two people loading the same page, and they contain no identifier. Hosts and never URLs: a third-party script URL routinely carries an account id or a session token.

Both fields switch off with data-panel-diagnostics="false". The diagnosis switches off with them, and that is the one trade-off on this page that is entirely yours to make.

  1. Another script had already wrapped history.pushState

    xPanel detects this

    There is no browser event that says "a single-page app just changed page". So every analytics tool wraps the same two history methods. Two wrappers on one method is the standard way a single-page count goes wrong: double counts when both fire, missing counts when one forgets to call through. We measure the state before we install ours, because afterwards the answer would always be yes.

  2. Two copies of the tag

    sPanel detects this

    Almost always one in the site template and one in a tag manager. The second refuses to start, so you are not double-counted — but our own host then shows up in the third-party script list, and one of them should go.

  3. A consent manager holding the tag

    sPanel detects this

    Some block every third-party script until a click that, more often than not, never comes. Panel does not need that consent: nothing is written to the device and nothing survives the night. So the tag can be allow-listed, or moved out of the managed block entirely. If your page views are a fraction of your server logs, this is the first thing to check.

  4. A tag manager injecting scripts after load

    sPanel detects this

    The s list is a snapshot taken when the first page view is sent, a few milliseconds after your document parses. Anything injected later is not in it. Waiting would delay the page view, and a delayed page view is a lost page view on every visitor who bounces.

  5. An A/B testing tool that reloads or redirects

    sPanel detects this

    A redirect on load manufactures two page views for one visit, and a test that rewrites the URL can manufacture more. The tool shows up in the list; which of your two measurements is right is your call.

  6. An ad blocker, or a filtering DNS resolver

    A blocked script sends nothing, and there is no way for us to know it happened. This is an undercount that affects every analytics tool including ours, and it is uneven: larger on a technical audience. Serving /v1/p.js from your own domain, with data-panel-host, is the usual answer.

  7. A Content-Security-Policy

    script-src has to allow the host serving /v1/p.js, and connect-src the host receiving the events — the same one, unless you set data-panel-host. If either is missing the script fails silently, and that is deliberate: it never writes to the console, never throws, and never appears in your error tracking. The price of that restraint is that it takes this line of documentation to catch it.

  8. A hash router, the #/cart kind

    Not followed automatically, and that is a refusal rather than a gap: counting it would mean sending the fragment, which is the half of a URL we drop on principle. Call panel.pageview('/cart') from your router instead. You decide what leaves your page, and we redact it on the way out anyway.

  9. Do Not Track and Global Privacy Control

    Honoured, so a small and self-selected slice of your visitors is not counted. Panel cannot follow anyone across sites or across days, so the signal arguably does not apply to us; a product whose whole argument is restraint does not get to claim that exemption.

  10. Your own development machine

    localhost, 127.0.0.1, *.local, *.test and file:// are not counted, because the first thing anybody does after pasting the tag is reload their dev server twenty times. To test an install, add data-panel-local="true" — an attribute, not the localStorage switch other tools offer, because Panel writes nothing to a visitor's device. That constraint is the product showing up in the API.

  11. Pages the browser prerenders

    Chrome loads pages in the background that the visitor may never open. Those are not counted until the page is actually shown.

  12. Bots

    Filtered server-side, from the user agent and the request rate, never in the browser: a filter placed in the page is one a headless browser turns off by lying.

Measure your traffic without asking for anything.

One script tag, no consent banner, and a server in the European Union. Your first page view appears within seconds.

No commitment. No qualification call.

The whole install

<script defer
  src="https://panel.raicode.tech/v1/p.js"
  data-panel-key="pk_live_…"></script>