Skip to content

Features

Cookieless analytics features

Day one: paste one script. The visit after Reject is on the board — pageviews, sources, campaigns. Then whether the hostname answered. Named goals, Claude, and serving the file from your domain sit further down.

Sample dashboard, last 30 days

View sample dashboard

Unique visitors

1,493

Total visits

1,493

Total pageviews

3,036

Views per visit

2.03

Bounce rate

30%

Visit duration

5m 6s

Seeded sample — the same six tiles after the paste.

For marketers

Start here. Growth funnels are a straight path on the same calendar day — midnight starts a new person. That is the privacy trade, not a hidden cap.

  • Now

    Campaigns with five UTM tags, not a query-string landfill

    utm_source, medium, campaign, content, term. Everything else is dropped in the browser.

    More

    Only those five tags leave the tab. Tokens and emails in other query keys never reach us. Inner pages in a single-page app do not inherit the landing UTMs. The dashboard groups campaigns, channels, ads, and keywords. Messy URLs stay messy on your site.

  • Now

    Six tiles on the curve, click to overlay

    Unique visitors, visits, pageviews, views per visit, bounce, duration. Line, bars, or auto.

    More

    The numbers sit on the chart. Click a tile to overlay. Compare draws the previous window as a dashed line. Unique visitors reset at midnight UTC. A visit is the same person within 30 minutes. Visit duration is last minus first pageview in that visit. One-page visits are 0 seconds. No heartbeat, no extra script.

  • Now

    Live — last page, five minutes

    Who is on which path right now, without a heartbeat.

    More

    People on the site in the last five minutes. The path is their last pageview. A tab left open does not ping. No extra script. The pill at the top of the dashboard jumps to the list.

  • Now

    Watch — hear about downtime first

    Around the clock we check that your site still answers. You should know before the client does.

    More

    They clicked Reject. The hostname still has to answer. HTTP, TLS, and DNS on a site you already added. Frankfurt every window; Dublin only if Frankfurt failed. Down means both failed. Slow is not down. A public status page and a monthly report. Visitor 404s via cfa('404'), 500s via cfa('500'). Down windows shade the analytics chart. No Watch SKU — it follows the invoice. Recipe: /docs/watch.

  • Now

    Goals — the action, billed as nothing

    A signup is a signup. It is not another pageview on the invoice.

    More

    Ask whoever ships the site to call cfa('signup') when the thing happens. There is no tag-manager UI yet. Events are not billed. Marketers without a developer still get pageviews, sources, and landing campaigns from the paste. Hobby and Starter keep a single named goal.

  • Now

    Monday in the inbox, not a login habit

    The briefing you already see, mailed once a week. No extra tracking.

    More

    Monday, once. Visitors, pageviews, top page and source from the last 7 days. Unsubscribe is a link. Switch it off in account settings. Not a daily drip.

  • Now

    Period briefing — a note you can paste to leadership

    The pattern, the mix, what to open next. Dashboard numbers only — no hashes.

    More

    Owner click. Grok writes from the tiles you already see. Copy it into a Monday email. Hobby 6 a day, Starter 12, Growth 20. Cached reads are free. The weekly mail is the same numbers, not a second tracker.

  • Now

    Notes on the curve — yours, or a campaign that just showed up

    You write a launch. The first day a UTM campaign arrives, we stamp it. Volume stays on the chart.

    More

    Vertical line, tooltip, list. Type a note, or we add “Campaign: …” the first time that campaign (or source / medium) is seen. Day-over-day traffic % is not a note — the tiles already show that. The Monday briefing is separate.

  • Now

    Search Console queries — clicks, impressions, CTR, position

    Sign in with the Google account that owns Search Console. Empty until then — no invented clicks.

    More

    Connect Google on the dashboard. You grant Search Console read on a site you own. We show totals plus top queries and Google landing pages for the same range as the board: clicks, impressions, CTR, and average position. Empty until you connect — we do not invent clicks.

  • Now

    Stripe charges on the same range

    Connect the Stripe account that takes payment. Empty until then — no invented euros.

    More

    OAuth, read-only. Gross, refunds, net, and charge count for the board range. If Checkout already stored utm_source / medium / campaign in metadata, we list those tags. Clicking one filters your pageviews. It does not prove that visit paid. Stripe account, not this hostname. Not mixed into visitor rows.

  • Now

    Locations: countries, regions, cities — still no café

    Click a country to zoom. Tabs for regions and cities. No map tiles, no postcode.

    More

    A country outline on the dashboard. Click to filter. Regions and cities are labels on the request, not a café database. No IP on the row, no coordinates, no postal code.

  • Now

    Many sites. Viewers. A public code.

    Starter: 5 sites, 3 viewers. Growth: 10 sites, 10 viewers. A public link plus a snippet for the website.

    More

    Invite colleagues at /app/team — they sign in and see dashboards, not settings. A public /s/… link needs no account. Viewers are included in the plan, not a seat SKU. Past 10 viewers, email hello@cookiefreeanalytics.com.

  • Now

    Funnels — linear, same day, Growth

    Drop-off between pages and events. Midnight starts a new person.

    More

    Growth only. Linear path, same UTC day. Midnight resets the person. That is the privacy trade, not a hidden cap. Two to eight steps: a path or a named event. Hobby and Starter keep a single named goal. No extra script. No session replay.

  • Now

    First byte and Lighthouse — lab, not invented LCP

    A Frankfurt GET we run. Google lab scores 0–100. The 1 KB script does not guess dwell or LCP.

    More

    First byte is four signals from a GET we make in Frankfurt, then what to do. Lighthouse (Performance, Accessibility, Best Practices, SEO) comes from PageSpeed Insights. Lab, not field. Refresh always runs a new audit. Scores are not cached. Not a Core Web Vitals product.

  • Now

    Buttons, files, outbound — named clicks, not heatmaps

    Opt-in. The 1 KB file stays 1 KB. A second helper counts the click.

    More

    Tick “also count clicks” in site settings. That adds a second file, cf-clicks.js: outbound hosts, file extensions (pdf, zip, …), buttons, and anything with data-cfa="…". Same beacon, named events, not billed. Not a heatmap. The default script does not listen.

  • Now

    Set up this site — from pages you already have

    A checkout path and goals from events that already fire. You confirm. Nothing invented.

    More

    The dashboard can read your homepage menu and the paths that already have pageviews. On Growth it can save same-day funnels if those URLs exist — not a second copy of one you already added. Goals still need a named event that already fired. Owner click, not a silent rewrite.

  • Now

    WordPress plugin

    Upload the plugin. Paste the public ID. Pageviews without a MonsterInsights cookie.

    More

    GitHub zip today; WordPress.org listing is later. WordPress.com cheap plans still paste the snippet. Serving the file from your own hostname so blockers miss us is optional — recipe on /docs. Topic: /wordpress-analytics.

For teams with tooling

Same numbers as the dashboard. No visitor hashes. Read API and MCP on /docs/api.

  • Now

    Serve the file from your hostname

    Optional. List-based blockers never see our hostname if the file comes from yours. On Hobby.

    More

    A path rewrite of /cf.js, or a c. CNAME. CNAME keeps visitor IP, city, and same-day uniques. Path rewrites recover blockers but collect through the host. Shopify and other closed builders: paste the snippet, then CNAME. Recipe: /docs/proxy.

  • Now

    Read API — the dashboard as JSON

    A hashed bearer. The same visitors, pages, sources, and live you already see.

    More

    Starter and Growth. Mint a token on Account. We store a hash, show the secret once. Sites, overview, pages, sources, live. Growth adds funnels. No visitor hashes. No raw pageview dump. Hobby stays the dashboard. Recipe: /docs/api.

  • Now

    MCP — the dashboard in Claude and Cursor

    Ask after a deploy. OAuth 2.1 at https://www.cookiefreeanalytics.com/mcp, or the hashed bearer.

    More

    Starter and Growth. Claude and Cursor sign in on this host. curl still uses the token from Account. The model sees aggregates, not hashes. Recipe: /blog/cookieless-analytics-mcp.

  • Now

    Export — Excel from the dashboard

    Pages, referrers, campaigns, and the rest of the board as a workbook. We do not keep the file.

    More

    The Excel button on the dashboard downloads what you are looking at. Account can export and delete. No sales call. No weekly dump we host for you.

For engineers

The moat is how we collect and how we forget. Full recipe on /methodology.

How do you count a unique visitor without cookies?
HMAC-SHA256 of site, IP and user-agent, keyed with a server pepper and a random salt that exists only for that UTC day. We store 16 bytes. The IP is discarded. Tomorrow the salt is deleted, so the same person is a new digest.
Why not just SHA-256(site + IP + UA + date)?
That is deterministic. Anyone with edge IP logs and the formula can remake Monday’s hash. A deleted random salt plus a pepper that is not in the database cannot. HMAC is the keyed primitive; concatenation is not. This is the visitor-side moat — recipe on /methodology.
How does the beacon leave the browser?
POST JSON via navigator.sendBeacon. The payload is not in the URL, so CDN access logs do not see ?email= or ?token=. The shipped script does not GET. Collect is POST only — GET returns 204 and writes nothing.
Which query parameters do you keep?
utm_source, utm_medium, utm_campaign, utm_content, utm_term. The script allowlists them. The server allowlists them again. Everything else is dropped before it leaves the tab.
Do you store the full referrer?
No. Origin + path, no query. Source labels (Google, Hacker News, Direct) come from the hostname. Tokens in a referrer query never land on the row.
Does it count client-side / SPA navigations?
Yes. pushState, replaceState, popstate. The same path is not counted twice. Campaign tags stick to the first pageview in the tab — inner routes do not inherit them. After the landing, the referrer we send is the previous path on your host, so those hits are Direct, not another HN session.
How small is the file with all of that?
Under 1 KB gzipped, measured from /cf.js on this build. HMAC is server-side. POST, the allowlist, SPA hooks, and a prerender skip are in the script. Auto click-listeners are not.
Do prefetch and bots inflate the count?
Prerender/prefetch is dropped in the script (document.prerendering) and again if Sec-Purpose says prefetch. Library and crawler user-agents are dropped. Hostname must belong to the site, except on the public demo. Trailing slashes are folded so /pricing and /pricing/ are one path.
Do you store city-level location?
The edge already classified the request. We copy country, ISO 3166-2 region (as US-CA so California is not Canada), and a city name. We do not store IP, latitude, longitude, or postal code, and we do not run MaxMind or any other geo-IP database. If the host sent nothing, the fields are empty.
What stops someone flooding /api/collect?
Public ingest is not unforgeable. 60 beacons per hashed IP per minute — one flood, not a busy site. The counter lives in Postgres, so every Frankfurt isolate shares it. Over that IP quota we return 204 and write nothing. A popular site is not dropped. Raw IP is not stored on that row.