MargioLink
Free and open source · MIT

Count the click, not the person.

MargioLink is a URL shortener with the analytics you actually wanted — country, city, device, browser, channel, campaign, hour of the day — and none of the surveillance you didn't. No IP address is stored, in any form. No tracking cookie is set. No visitor can be followed from one day into the next, by us or by anyone who takes the database.

One Cloudflare Worker One D1 database No third-party requests GDPR by design
link.example.com/app
The MargioLink overview: clicks and unique visitors over ninety days, top links, a world map, device and channel breakdowns, and an hour-by-weekday heatmap.

The record

Everything a click writes down, and everything it never does.

Both columns are enforced by the schema and by tests, not by a promise in a footer. The right-hand column is the interesting one: those fields do not exist in the database, so there is nothing to leak, subpoena, or change our minds about later.

Recorded

Fifteen dimensions, all of them about the request.

  • Time, to the second
  • Country, region, city, time zone
  • Cloudflare datacenter and network operator
  • Device type, operating system, browser
  • Preferred language
  • Referring site and channel
  • UTM source, medium, campaign, term, content
  • Scan or click, and how the request was answered

Never recorded

No column exists for any of these.

  • Your IP address, raw or hashed at rest
  • Your full user-agent string
  • A tracking cookie
  • An identifier that outlives the UTC day
  • The path or query of the page that referred you
  • A profile, a score, or a decision about you
  • A request to any third party, ever

The rotation

One visitor, one day, one code that expires.

To tell two visitors apart without knowing either of them, MargioLink derives a short code from the request, keyed to today's date. The key changes at midnight UTC and the old one is never kept, so the same person arriving tomorrow is a stranger again.

That is the whole trick, and it is also the whole limit: unique visitors are counted per day and never across days, because the design that protects the visitor is the same design that refuses to answer the question.

Read the privacy notice

How a visitor code is derived
ip 203.0.113.42 discarded
user-agent Mozilla/5.0 (Macintosh…) discarded
slug/launch
daily keysecret : 2026-09-03
visitor 9f2c41e0b7a3d58e

Rotates at 00:00 UTC

The dashboard

A real analytics app, not a table of counters.

Served by the same Worker at /app. Type, icons and the map's own topology all ship in the build, so the dashboard makes no request to anyone else either. Every chart has a table view, every control is keyboard-operable, and the whole thing is built to WCAG 2.2 AA.

The links list: search, status and tag filters, a seven-day sparkline on every row, and one-tap copy.
Links — instant search, tag and status filters, a sparkline per row, ⌘K from anywhere.
A single link's detail page: clicks, unique visitors, scans and bot share, with the click history charted over ninety days.
Per link — every dimension as a ranked list, an hour-by-weekday heatmap, a live feed and the QR code.

What it does

Links that behave

Custom or generated slugs, a password gate, an expiry with its own destination, an off switch, tags, and a soft delete you can undo.

Fast redirects

One D1 lookup at the edge, then a 302. Recording the click happens after the response is on its way, so measurement never costs the visitor a millisecond.

Fifteen dimensions

Country, city, device, OS, browser, language, network, referrer, channel, three UTM fields, weekday × hour, scan-or-click, and outcome.

QR codes that count

Download as SVG or PNG. A scan carries its own marker, so the poster and the newsletter never end up in the same bucket.

Your data, exportable

CSV straight from the dashboard, a documented JSON API, and a database you own. Nothing here is a hostage.

It forgets on schedule

Hourly rollups, a nightly job that deletes raw rows past the retention window, and aggregates that identify nobody kept after.

Run your own

Five minutes, your Cloudflare account, no bill.

There is no hosted version to sign up for and no plan to outgrow. You deploy it, you hold the database, and on Cloudflare's free tier a personal or small-team shortener costs nothing to run.

# clone, install, create the database
$ git clone https://github.com/amargiovanni/margiolink
$ cd margiolink && npm ci
$ npx wrangler d1 create margiolink

# put the id in wrangler.jsonc, then
$ npm run db:migrate
$ npx wrangler secret put HASH_SECRET
$ npm run deploy
  1. Bring your own domain

    Point a hostname at the Worker as a custom domain and wrangler creates the DNS record and the certificate for you.

  2. Set three secrets

    An admin username and password, and a hashing secret of at least 32 characters. A missing hashing secret fails closed — the redirect refuses rather than quietly recording reproducible visitor codes.

  3. Seed a demo, or start empty

    npm run db:seed:demo fills a local database with six months of plausible traffic, so you can see every chart working before your first real click.

  4. Keep it current

    Migrations are reversible and verified in CI, so pulling a new version is a git pull and a deploy rather than an evening.

Free, and open

Licence
MIT
Use it commercially, fork it, rebrand it. No clause waiting for you.
Tests
700+
Inside workerd against a real D1, in a DOM, and end-to-end in a real Chromium.
Third parties
0
No CDN, no font host, no analytics on the analytics.
Moving parts
2
A Worker and a D1 database. Nothing else to operate.

Built to be read, not just used.

The whole backend is a few thousand lines of TypeScript with no ORM, no framework beyond a router, and no service to sign up for. The privacy notice, the data map, the legal basis and the accessibility sweep are all in the repository, versioned with the code that has to honour them — because a privacy claim you cannot check is just marketing.

Stack
Cloudflare Workers · D1 · Hono · React 19 · Tailwind 4
Privacy
No IP stored · daily-rotating visitor code · 180-day raw retention
Access
WCAG 2.2 AA · keyboard throughout · every chart has a table
Security
Coordinated disclosure policy · security.txt · reversible migrations