A standalone port of GoatCounter to run entirely on Cloudflare's edge infrastructure.
CloudCounter brings GoatCounter's privacy-focused analytics to Cloudflare Pages + D1, requiring no external servers or databases. Deploy once and track pageviews across your sites with a single JavaScript snippet.
- Pageview tracking with session deduplication
- Top pages, referrers, browsers, operating systems, locations
- Screen size breakdown
- Event tracking support
- Privacy-first: no cookies, session hash only
- Server-rendered dashboard
- Data retention policies
Additional screenshots available in the media folder.
- Node.js 18+
- Wrangler CLI
- Cloudflare account with Pages and D1 access
git clone https://github.com/philippdubach/cloudcounter.git
cd cloudcounter
npm install# Create D1 database
wrangler d1 create cloudcounter
# Create KV namespace for sessions
wrangler kv namespace create SESSIONSEdit wrangler.toml and add your database_id and KV namespace id:
[[d1_databases]]
binding = "DB"
database_name = "cloudcounter"
database_id = "YOUR_DATABASE_ID_HERE"
[[kv_namespaces]]
binding = "SESSIONS"
id = "YOUR_KV_NAMESPACE_ID_HERE"wrangler d1 execute cloudcounter --file=./migrations/0001_init.sqlwrangler secret put DASHBOARD_PASSWORDwrangler pages deploy public<script async src="https://your-analytics.pages.dev/count.js"></script># Start local dev server
npm run dev
# Apply migrations to local D1
npm run db:migrate:local| Variable | Description | Default |
|---|---|---|
SITE_NAME |
Dashboard title | "My Analytics" |
DATA_RETENTION_DAYS |
Days to keep data (0 = unlimited) | "0" |
| Secret | Description |
|---|---|
DASHBOARD_PASSWORD |
Password for dashboard access |
The script auto-tracks pageviews on load:
<script async src="https://your-analytics.pages.dev/count.js"></script>// Custom pageview
cloudcounter.count({ path: '/custom-path', title: 'Custom Title' });
// Event tracking
cloudcounter.count({ path: 'button-click', title: 'Sign Up Button', event: true });Add data-cloudcounter-click attribute to elements:
<button data-cloudcounter-click="signup-btn">Sign Up</button>cloudcounter.skip(); // Disable for this session
cloudcounter.enable(); // Re-enableGET/POST /api/count?p=/path&t=Title&r=referrer
| Parameter | Description | Required |
|---|---|---|
p |
Page path | Yes |
t |
Page title | No |
r |
Referrer URL | No |
e |
Event flag (1 or true) | No |
s |
Screen width | No |
Returns: 1x1 transparent GIF
cloudcounter/
├── functions/ # Cloudflare Pages Functions
│ ├── _middleware.ts # Auth guard
│ ├── _worker.ts # Cron handler
│ ├── api/count.ts # Hit tracking endpoint
│ ├── index.ts # Dashboard
│ ├── login.ts # Login page
│ └── logout.ts # Logout
├── src/
│ ├── lib/ # Core utilities
│ └── stats/ # Query functions
├── public/
│ ├── count.js # Tracking script
│ ├── dashboard.js # Dashboard charts
│ └── dashboard.css # Dashboard styles
└── migrations/ # D1 schema
| Table | Description |
|---|---|
paths |
Normalized page paths |
refs |
Referrer URLs |
browsers |
Browser names |
systems |
Operating systems |
hits |
Raw pageview data |
hit_counts |
Hourly aggregates |
hit_stats |
Daily aggregates |
browser_stats |
Browser breakdown |
system_stats |
OS breakdown |
location_stats |
Country breakdown |
size_stats |
Screen size breakdown |
Daily at 3 AM UTC:
- Apply data retention policy
- Vacuum unused referrers
- Clean orphaned dimension records
This project is a port of GoatCounter by Martin Tournoij.
Licensed under the EUPL.


