Skip to content

IAMDevBox/cloudflare-url-shortener

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

2 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

cloudflare-url-shortener

A production-ready, self-hosted URL shortener built with Cloudflare Workers and KV storage. Zero cost, global edge performance, and full CRUD admin API with click analytics.

πŸ“– Full tutorial: Building a Self-Hosted URL Shortener with Cloudflare Workers

Features

  • Short URL redirects β€” /s/{code} β†’ full URL with automatic UTM parameter injection
  • Click analytics β€” per-source tracking (twitter, linkedin, direct, etc.)
  • Full CRUD API β€” create, list, update, soft-delete, restore, permanent-delete
  • API key authentication β€” Bearer token for all write operations
  • Campaign tagging β€” organize URLs by campaign for GA4 segmentation
  • Notes field β€” admin annotations per URL
  • Soft delete + restore β€” data is preserved until you permanently remove it
  • CORS support β€” works with any frontend admin dashboard
  • Zero cost β€” fits within Cloudflare's free tier (100k requests/day)

Architecture

User β†’ /s/{code} β†’ Cloudflare Worker (edge, <15ms) β†’ KV lookup β†’ 302 redirect + UTM
                                                     ↓
                                               Stats tracking (async)

Quick Start

1. Clone and install

git clone https://github.com/IAMDevBox/cloudflare-url-shortener.git
cd cloudflare-url-shortener
npm install

2. Create a KV namespace

wrangler kv:namespace create URL_MAPPINGS
# Copy the id from the output

3. Configure wrangler.toml

Replace the placeholders:

[[kv_namespaces]]
binding = "URL_MAPPINGS"
id = "YOUR_KV_NAMESPACE_ID"   # from step 2

[[routes]]
pattern = "yourdomain.com/s/*"
zone_name = "yourdomain.com"

4. Set your API key

wrangler secret put API_KEY
# Enter a strong secret when prompted

5. Deploy

wrangler deploy

That's it β€” your URL shortener is live at https://yourdomain.com/s/*.

API Reference

All write endpoints require Authorization: Bearer YOUR_API_KEY.

Create a short URL

curl -X POST https://yourdomain.com/api/shorten \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/very/long/blog/post/",
    "code": "oauth",          # optional custom code (auto-generated if omitted)
    "campaign": "blog_post",  # UTM campaign tag
    "notes": "OAuth guide"    # optional admin note
  }'

Response:

{
  "shortUrl": "https://yourdomain.com/s/oauth",
  "code": "oauth",
  "longUrl": "https://example.com/very/long/blog/post/"
}

Get click statistics

curl https://yourdomain.com/api/stats/oauth

Response:

{
  "total": 142,
  "sources": {
    "twitter": 98,
    "linkedin": 32,
    "direct": 12
  },
  "lastAccess": "2025-11-27T10:30:00Z"
}

List all active URLs

curl https://yourdomain.com/api/urls \
  -H "Authorization: Bearer YOUR_API_KEY"

Update a URL

curl -X PUT https://yourdomain.com/api/urls/oauth \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/new-url/", "campaign": "updated"}'

Soft delete (recoverable)

curl -X DELETE https://yourdomain.com/api/urls/oauth \
  -H "Authorization: Bearer YOUR_API_KEY"

Restore a deleted URL

curl -X POST https://yourdomain.com/api/urls/oauth/restore \
  -H "Authorization: Bearer YOUR_API_KEY"

Permanent delete (irreversible)

# Must soft-delete first, then permanently remove
curl -X DELETE https://yourdomain.com/api/urls/oauth/permanent \
  -H "Authorization: Bearer YOUR_API_KEY"

Python Client

The included client.py provides a typed Python wrapper:

from client import URLShortenerClient

client = URLShortenerClient()  # reads SHORTENER_API_KEY + SHORTENER_BASE_URL from env

# Create
short = client.create("https://example.com/post/", campaign="twitter")

# Analytics
stats = client.get_stats("oauth")
print(stats["total"])  # 142

# List all
for url in client.list_urls():
    print(f"/s/{url['code']} β†’ {url['url']}")

Environment variables:

export SHORTENER_API_KEY=your-secret-key
export SHORTENER_BASE_URL=https://yourdomain.com

Testing

Run the full integration test suite against a local dev instance:

# Terminal 1: start local worker
npx wrangler dev --local

# Terminal 2: run tests
API_KEY=test-key node test_worker.js

Or against your deployed worker:

BASE_URL=https://yourdomain.com API_KEY=your-key node test_worker.js

UTM Parameter Injection

Every redirect automatically injects UTM parameters:

/s/oauth?s=twitter
    ↓
https://example.com/post/?utm_source=twitter&utm_medium=shortlink&utm_campaign=blog_post&utm_content=oauth

The ?s= query parameter sets utm_source. Omit it to get utm_source=short (default).

Cost Analysis

Cloudflare Workers free tier per day:

  • 100,000 Worker requests
  • 1GB KV storage
  • 100,000 KV reads + 1,000 KV writes

A typical blog gets ~500 short URL clicks/day β€” well within free tier.

Service Monthly cost
Bitly Pro $29
TinyURL Pro $9.99
This solution $0

Related Resources

License

MIT β€” use freely in your own projects.

About

Self-hosted URL shortener with Cloudflare Workers + KV storage. Zero cost, global edge, UTM tracking, click analytics, full CRUD API.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors