Personal link shortener on Cloudflare Workers + D1. Serves https://t.string.sg/<slug>
redirects at the edge and records every clickthrough with the medium it came from
(WhatsApp, Telegram, Instagram, Facebook, X, LinkedIn, TikTok, WeChat, LINE, Slack,
Discord, email, search, direct, …), referrer, country, device, OS, and browser.
https://t.string.sg/admin — password-protected (single ADMIN_PASSWORD Worker secret).
- Create links with a custom slug or a random 6-char code
- Bulk add: paste a Markdown table or CSV/TSV (format detected by a fixed rule), preview each row's status, then create the new ones; existing slugs are never overwritten
- Per-link stats: clicks over time, breakdowns by medium / referrer / country / device / browser
- Link-preview crawlers (WhatsApp/Telegram/Slack unfurlers, Googlebot, …) are still
redirected but flagged
is_bot=1and excluded from stats; the Bots toggle shows them
WhatsApp and Telegram open links in the system browser with no referrer, so human
clicks from them are recorded as direct — only their link-preview bots are positively
attributed. This is an industry-wide constraint, not a bug. The raw user-agent of every
click is stored (clicks.user_agent), so detection rules in src/detect.ts can be
extended and old rows reclassified with an UPDATE.
npm install
npm run db:migrate:local # local D1 in .wrangler/state
npm run dev # http://localhost:8787, password from .dev.vars
npm test # classifier unit testsnpx wrangler login
npx wrangler d1 create t-string-sg # paste database_id into wrangler.jsonc
npm run db:migrate:remote
npx wrangler secret put ADMIN_PASSWORD
npm run deploy # attaches t.string.sg automaticallyAd-hoc production queries:
npx wrangler d1 execute t-string-sg --remote --command "SELECT ..."src/detect.ts— UA/referrer → medium classification (the interesting part; tested)src/redirect.ts— hot path: D1 lookup, 302no-store, click logged viawaitUntilsrc/db.ts— all SQL; stats bucketed in Singapore timesrc/auth.ts— HMAC session cookie derived fromADMIN_PASSWORDsrc/api.ts— JSON/form API for links + statssrc/admin/— server-rendered dashboard, zero client build