For installation paths and the measurement levels, visit the edgeglint project page.
edgeglint is our promise of privacy-aware web analytics on Cloudflare: more insight than Cloudflare Web Analytics through scroll depth, click counts per element, JavaScript errors and Core Web Vitals, without analytics cookies or visitor identifiers. Our current country rule excludes the EEA, UK, Switzerland, unknown countries and Tor from edgeglint measurement. That is a deliberately conservative product boundary, not a finding that collection elsewhere needs no consent or becomes lawful when you remove cookies.
I’m Sam Jacobson at Dataxad. We built edgeglint because we wanted to understand our own website without building a history of the people using it. The public edgeglint repository opens today. This is the research, the trade-offs and the deployment experience behind it.
Dashboard preview with sample data. These figures are not Dataxad traffic.
Why build another analytics tool?
Cloudflare Web Analytics is a good baseline, but it does not answer our interaction questions. It is free on all plans, uses neither cookies nor localStorage, and includes Core Web Vitals. With automatic setup on a proxied site, beacons go to the site’s own domain; the script still comes from static.cloudflareinsights.com. It lacks scroll depth, click data, heatmaps and JavaScript errors. Its FAQ still says custom events are not available.
Dataxad’s question was specific: can we learn which page elements get used, how far readers scroll and where JavaScript fails, without identifying visitors? edgeglint keeps page-level measurements and coarse aggregates rather than a person-level timeline. Its first version has no unique-visitor metric, replay or cursor tracking. It also gives up traffic visibility in entire regions rather than assume that a cookie-free beacon is exempt from consent. That is the exchange Sam Jacobson and Dataxad chose: more useful interaction data where collection is permitted, less coverage where the legal basis remains uncertain. “Better” here means better suited to that particular job, not more features than every analytics platform. Cloudflare already provides Core Web Vitals; our addition is to put performance alongside the interaction and error measurements we needed, under a collection policy we can inspect and test.
The surrounding Cloudflare products did not close the gap. The old Apps marketplace is gone; third-party migration notices report discontinuation on August 31, 2024, with new installs stopped by January 15, 2025. Deploy buttons and Cloudflare’s templates remain, but the template collection had no analytics template in our research snapshot.
Custom Dashboards do not list Analytics Engine among their supported data sources. Zaraz manages third-party tools and consent; it is not the first-party collector we needed. A custom managed component cannot expose a public endpoint.
What did we find in the market?
There were useful building blocks, but none matched our exact storage and collection choices. The table records our October 21, 2026 snapshot, not live star counts. NV means not verified in our research brief, including feature cells: an NV is not evidence that a feature is absent. S/C/E/R means scroll depth, element clicks, JavaScript errors and session replay.
| Tool / source | License | Stars, checked 2026-10-21 | Storage | Visitor counting | S / C / E / R |
|---|---|---|---|---|---|
| Counterscale | MIT | 2,166 | Analytics Engine; R2 archive | Browser HTTP cache | No / No / NV / NV |
| GT Analytics | MIT | NV | NV; Counterscale fork | Daily site-scoped HMAC key | NV / NV / NV / NV |
| InsightFlare | MIT | 646 | D1/KV; Analytics Engine optional | localStorage ID unless EU mode | NV / NV / NV / NV |
| HanAnalytics | MIT | 278 | Analytics Engine | NV | NV / NV / NV / NV |
| Plausible | AGPL-3.0; JS MIT | 29,583 | ClickHouse, self-hosted | Daily hash: salt, domain, IP, UA | Yes / NV / NV / NV |
| Umami | MIT | 39,504 | NV | Hash; salt rotates monthly by default | No / NV / NV / No |
| Rybbit | AGPL-3.0 | 13,147 | NV | NV; advertises no cookies | NV / NV / Yes / Yes |
| Cloudflare Web Analytics | NV | NV | Cloudflare-managed | Referrer-based visits; unique counting NV | No / No / No / NV |
| Microsoft Clarity | NV | NV | NV | NV; regional cookie consent required | NV / NV / NV / NV |
| PostHog cookieless | NV | NV | NV | Server-side hash | NV / NV / NV / NV |
| edgeglint | Not yet chosen | NV | Analytics Engine | Referrer-based visits; no unique ID | Yes / Yes / Yes / No |
Some distinctions matter more than stars. Plausible deletes its daily salt every 24 hours and has built-in scroll depth. Umami hashes website, IP, User-Agent, salt and a server secret. InsightFlare supports custom events and Web Vitals. Rybbit offers funnels, errors, replay and cloud Web Vitals. Those are different product choices, not interchangeable definitions of privacy. Our brief found no heatmaps in Plausible, Umami or Rybbit.
Clarity’s FAQ says explicit consent is required before placing cookies in the EEA, UK and Switzerland. PostHog explains that a persistent distinct ID is personal data under GDPR. Even Mouseflow’s GDPR page, alongside its compliance language, says customers may need active, explicit consent. Marketing labels cannot settle a deployment’s legal basis.
Why not fork Counterscale?
Counterscale was the closest architectural match: Workers, Analytics Engine, a site index and an R2 archive. But its cache-based visit counting reads If-Modified-Since and sets Last-Modified; its collector also stores raw User-Agent. Those choices conflict with our identifier-free boundary.
It was also a substantial application: about 8,500 non-test lines and 11,100 test lines, including a React Router dashboard and installer. Its contribution guidance describes a volunteer project; our snapshot saw external PRs take four days to four months and a security PR still open from October 16. We wanted a smaller, different contract, not a fork whose core assumptions we would undo. dytsou/site also demonstrated click heatmap cells in Analytics Engine; we chose element counts without coordinates instead.
Does cookie-free mean consent-free?
No. Removing cookies does not by itself remove ePrivacy rules. EDPB Guidelines 2/2023, adopted in their revised form on October 7, 2024, treat locally produced information sent to a server as access to terminal information. Their scope includes caching mechanisms and, in some circumstances, IP-only tracking. Applicability does not automatically mean consent is required; a relevant exemption still needs assessment.
That distinction shaped edgeglint more than any feature comparison. Aggregation is valuable, but does not automatically take a JavaScript beacon outside Article 5(3). WP29 Opinion 04/2012 says first-party analytics cookies are not strictly necessary, while recognizing limited risks with aggregation, opt-out and anonymisation. The CJEU’s Planet49 case says the consent rule applies whether or not information is personal. Breyer establishes that dynamic IP addresses can be personal data. For Sam Jacobson and Dataxad, those sources support caution about the collection mechanism itself, not merely promises about the final database. They do not establish that edgeglint qualifies for an exemption. We chose a country-code exclusion for regions where the consent question is especially sensitive; each operator still needs to assess the law for every place where measurement is enabled.
National rules add complexity:
- Germany: our research found section 25 TDDDG requires consent for storing or reading device information, with no general DSK exemption for reach measurement.
- UK: the statistical-purposes exception in PECR Schedule A1, in force from February 5, 2026, includes an objection requirement. Paragraph 5(2) excludes automatically emitted information. Whether HTTP headers fall within that wording remains an open question; see the ICO guidance on exceptions and the amending legislation.
- France: CNIL’s current guidance allows a consent exemption only when audience measurement meets defined conditions. It calls for anonymous statistics used only for the publisher’s own site or app, without cross-site tracking or combining data with other processing. The publisher must assess the configured tool and document its basis for any exemption; a product’s privacy label is not enough.
Our decision is operational: Cloudflare’s request country code controls whether the edge serves an empty tracker script and drops beacons from the EEA, UK, Switzerland, unknown countries and Tor. A visitor’s physical location may differ from that code. Cloudflare itself offers an EU-exclusion setting, but our rule covers more country codes. This prevents edgeglint measurements for those requests; it does not prevent Cloudflare from handling the page request or keeping operational logs. We do not describe edgeglint as “compliant” or “banner-free.”
What does edgeglint actually store?
Only the current Tier 1 measurement set is active. Measurement tier names do not establish a legal exemption. The stored fields are deliberately bounded:
- Normalized paths: allowlisted segments, identifier-like segments replaced by
:x, and a build-generated route list that maps unknown paths to/other. - Referrer registrable domain or
other; country; viewport width groups/m/l; bounded browser and OS major versions. - Scroll in 10-percent steps; foreground dwell in five-second steps; quick exit under five seconds, decided at
pagehide. - LCP in 100-millisecond steps and CLS in 0.01 steps: coarse loading and layout-stability measurements.
- JavaScript error class from a fixed list and a normalized script path, never the error message.
- Click counts by element path, using a 63-tag allowlist and
nth-childonly; outbound registrable domain or download extension.
We never store IP addresses, raw User-Agent, query strings, page text, form contents, element IDs or classes, cursor traces, x/y coordinates, replay or visitor identifiers of any lifetime. IP and User-Agent still reach the edge for country, bot checks, rate limits and version extraction, then are discarded. “Never stored” does not mean “never processed.”
There are no analytics cookies or localStorage. Two separate user actions can use cookies: a visitor’s opt-out sets a chosen 13-month preference cookie; dashboard login sets an authentication cookie. Neither is a visitor-counting mechanism. Global Privacy Control and Do Not Track stop both tracker and collector.
Visits follow Cloudflare’s definition: a pageview whose referrer domain differs from the site. There is no unique-visitor identifier in v0.1. The delta-flush model sends the pageview once; later tab-hidden or page-exit beacons carry new clicks, errors, outbound clicks and foreground dwell, and update the dashboard when scroll, LCP or CLS reaches a higher value. Corrections use the later report time. Back/forward cache restores continue the same page load.
Canary tests inject marker strings into IDs, classes, text, attributes, error messages, referrers and URLs. A marker in a beacon or stored row fails the test. The project also runs a Playwright canary on the real site. These checks test a specific leakage boundary, not universal anonymity.
Keyboard focus or pointer hover reveals exact chart values. This sample data does not represent visits to dataxad.com.
How does it run on Cloudflare?
Workers handle collection; Analytics Engine stores the bounded measurements; a single HTML dashboard queries them through the Analytics SQL Workers binding. The binding needs no API token and is available with Wrangler 4.145.0 or later.
The flow is straightforward:
- The edge applies region and preference checks before serving a useful tracker.
- The tracker sends permitted measurements; the collector validates and normalizes them.
- Analytics Engine receives rows; the dashboard reads aggregate answers.
- A public health probe uses hashed tokens; an owner-only diagnostic route supports investigation.
Our measured tracker was 2,471 bytes gzip against a 3,072-byte budget. The collector responds with 204 in every case, uses rate-limit bindings and writes at most 171 rows per flush. Missing DASH_KEY closes the dashboard with a 404; answers have a 60-second cache.
Analytics Engine’s limits include 20 blobs, 20 doubles, one index, 250 writes per invocation, 16 KB of blobs per request and three-month retention. The pricing page in our research listed 100,000 free writes and 10,000 free reads daily, while saying billing was not currently applied. Those are dated platform terms, not a promise of permanently free hosting.
Six lessons from six live deploy rounds
The architecture was small. Getting its boundaries right was not.
- Enable Analytics Engine in the account dashboard first. No API token can perform that initial step. Our first deployments stopped with “You need to enable Analytics Engine.”
- Referrer policy affected login. Astro rejected the form POST when
no-referrerproducedOrigin: null. The login page now usessame-origin. - Bundling changed executable code. Serializing a function with
toString()broke under esbuild’skeepNames:__name is not defined. The client became a template string, backed by a bundling test. - The SQL binding was not the SQL dialect we assumed. Sampling weights are already applied;
_sample_intervalis absent.date_truncis absent, whiletoStartOfInterval,toStartOfDayandtoDatework. Grouped-column ordering with aliases also failed. An owner-only diagnostic route tested 18 query shapes live. - Independent reviewers kept finding real defects. Across five rounds: patterns instead of allowlists, one-shot flushes, fail-open geography, double sample weighting,
SELECT DISTINCT, cache keys, back/forward cache and identifier-like domain labels. Small code did not make review optional. - The quota alert belonged to another workload. The collector used about 180 Worker requests daily; a game-host heartbeat elsewhere in the account used 100,000. Account-level alarms need workload-level diagnosis.
How can you use it, and what comes next?
Start with the repository’s setup guide. It covers three deployment shapes: a standalone Worker, integration into an existing Worker or Astro application, and Pages Functions. Use the current configuration instructions rather than copying a fragment from a blog. Read docs/research.md for the underlying comparisons, sources and decisions.
Next are a CNIL-oriented mode and unique-visitor counting only after counsel, plus a consent-manager hook. These are unfinished items, not capabilities promised by today’s dashboard.
For Dataxad, the point is useful website insight with a collection boundary we can explain, inspect and challenge. The repository has no license yet; we will choose one. Public source availability is not an open-source license grant.