Track visits to your GitHub projects, websites, and emails with a single invisible image. Generate shields.io-style badges with live counters — fully self-hosted, privacy-first.
TypeScript Express React Prisma SQLite License
Quick Start · Use Cases · Dashboard · API Reference · Configuration
You built something — a project, a portfolio, a tool. You shared it. But then... silence. Did anyone actually look at it? Did that recruiter open your email? Is anyone reading your documentation?
You have no way of knowing.
GitHub shows clone counts but not README views. Your personal website needs an expensive analytics tool. Emails give you zero feedback unless you pay for a marketing platform.
PixelPulse solves this with the simplest trick in web tracking: a 1x1 invisible image. Embed it anywhere that renders images — and the moment someone opens that page, you know.
| Scenario | How it works |
|---|---|
| GitHub project | Add a pixel to your README.md — every time someone opens the page, you get a visit |
| Personal website | Embed a pixel or badge on any page to track real visitors vs. bots |
| Email outreach | Add an invisible pixel to your HTML email — know exactly who opened it and when |
| Documentation | Track which docs pages get read and which are ignored |
| Multiple projects | Each pixel gets a unique label — track everything from one dashboard |
- Visit count — total and unique visitors per label
- Geography — which countries your visitors come from
- Devices — browser, OS, device type breakdown
- Bot detection — separate human traffic from Googlebot, bingbot, and crawlers
- Referrers — where your visitors are coming from
- Timeline — visit trends over days, weeks, and months
- Badges — customizable shields.io-style SVGs with live counters to show off in your README
- Why PixelPulse?
- Use Cases
- Quick Start
- Dashboard
- Badge Creator
- API Reference
- Badge Reference
- Configuration
- Architecture
- Tech Stack
- Privacy
- License
Someone clones your repo and opens the README? You'll know.
<!-- Add this anywhere in your README.md — it's invisible --> 
Want a visible counter instead? Use a badge:

Add a pixel to your HTML emails. When the recipient opens the email and their client loads images, you get a notification in your dashboard.
<img src="https://your-server.com/pixel/recruiter-email-jan.gif" alt="" width="1" height="1" style="display:none" />
Embed a pixel on any page of your site. Unlike heavyweight analytics tools, this adds zero JavaScript and zero latency to your page.
<!-- In your HTML <body> --> <img src="https://your-server.com/pixel/portfolio-home.gif" alt="" width="1" height="1" />
Every pixel gets a unique label. Track all your projects, pages, and emails from a single dashboard:
/pixel/project-alpha.gif → tracks Project Alpha README
/pixel/portfolio-about.gif → tracks your About page
/pixel/email-cold-outreach.gif → tracks a specific email campaign
/badge/my-tool.svg → visible badge with live counter
git clone https://github.com/Mykle23/pixel-pulse.git
cd pixel-pulse# Backend cd backend pnpm install # Frontend cd ../frontend pnpm install
cd ../backend
cp .env.example .envOpen .env and configure at minimum the IP_SALT. Optionally set API_KEY to protect the stats endpoints:
# Required — random string for IP hashing IP_SALT=my-super-secret-random-salt # Optional — protects /api/* routes API_KEY=my-dashboard-password # Database (defaults to SQLite file in project root) DATABASE_URL=file:./dev.db
npx prisma generate npx prisma db push
# Terminal 1 — Backend (port 3001) cd backend pnpm dev # Terminal 2 — Frontend (port 5173) cd frontend pnpm dev
Open http://localhost:5173 to access the dashboard. The frontend proxies API calls to the backend automatically.
Add this to any GitHub README or HTML page (replace with your server URL):

Open the page, then check the dashboard — your visit should appear within seconds.
PixelPulse includes a full analytics dashboard built with React:
- Overview — Total visits, unique visitors, human vs. bot traffic at a glance
- Label Management — Search, sort, paginate, multi-select, and bulk delete labels
- Label Detail — Per-label drill-down with timeline, device/browser/OS breakdown, bot list, and referrers
- Analytics page — Cross-label visualizations including:
- Label treemap (sized by visits, colored by growth)
- Stacked area timeline comparing top labels
- Interactive world map with geographic distribution
- Browser, OS, and device distribution (pie charts)
- Hourly activity chart
- Top referrers ranking
The dashboard includes a visual badge editor with live preview. You can:
- Choose between counter (live visit count), static (custom text), and pixel (invisible tracking) modes
- Customize label, message, colors, style, logo (any Simple Icons icon), and link URL
- Copy ready-to-paste Markdown, HTML, or URL snippets
- Browse a gallery of ~200 pre-configured developer badges (TypeScript, React, Docker, AWS, etc.) and apply them with one click
Badge examples:
# Live visitor counter
/badge/my-repo.svg?label=visitors&color=green&logo=github
# Static tech badge
/badge/preview.svg?label=TypeScript&message=5.9&color=3178C6&logo=typescript&logoColor=white&preview=true
# Message-only badge
/badge/preview.svg?label=&message=Hello+World&color=blue&preview=true
# Icon-only badge
/badge/preview.svg?label=&color=black&logo=github&logoColor=white&preview=true
# Clickable badge
/badge/my-repo.svg?label=GitHub&message=Star&color=yellow&logo=github&link=https://github.com/Mykle23/pixel-pulse
Full parameter reference in the Badge Reference section.
| Endpoint | Content-Type | Description |
|---|---|---|
GET /pixel/:label.gif |
image/gif |
1x1 transparent GIF pixel |
GET /pixel/:label.svg |
image/svg+xml |
1x1 transparent SVG pixel |
GET /badge/:label.svg |
image/svg+xml |
Dynamic SVG badge with visit counter |
All pixel and badge endpoints:
- Return
Cache-Control: no-cache, no-store, must-revalidate - Register the visit asynchronously (response is sent first, zero added latency)
- Are rate-limited to 100 requests/IP/minute
Data collected per visit:
| Field | Source |
|---|---|
ipHash |
SHA-256 of IP + salt (anonymized) |
country, city |
Local GeoIP lookup (MaxMind) |
browser, os, deviceType |
User-Agent parsing |
isBot, botName |
Bot detection |
referer |
Referer header |
Returns an overview across all labels. Rate-limited to 120 requests/IP/minute.
{
"totalVisits": 1420,
"uniqueVisitors": 389,
"botVisits": 230,
"labels": [
{
"label": "portfolio-home",
"total": 800,
"unique": 210,
"bots": 120,
"lastSeen": "2026年02月11日T18:30:00.000Z",
"topCountry": "ES"
}
]
}Returns detailed stats for a single label.
| Param | Default | Description |
|---|---|---|
from |
— | Start date (YYYY-MM-DD) |
to |
— | End date (YYYY-MM-DD) |
includeBots |
true |
Set to false to exclude bot traffic |
{
"label": "portfolio-home",
"total": 800,
"unique": 210,
"bots": 120,
"timeline": [
{ "date": "2026年02月10日", "visits": 45, "unique": 18 }
],
"countries": [{ "country": "ES", "visits": 300 }],
"devices": [{ "deviceType": "desktop", "visits": 600 }],
"browsers": [{ "browser": "Chrome 121", "visits": 400 }],
"topBots": [{ "botName": "Googlebot", "visits": 80 }],
"referers": [{ "referer": "google.com", "visits": 150 }]
}Batch delete labels and their associated visits.
curl -X DELETE http://localhost:3001/api/stats \ -H "Authorization: Bearer your-api-key" \ -H "Content-Type: application/json" \ -d '{ "labels": ["old-label", "test-label"] }'
Returns aggregated analytics across all labels for the specified period.
| Param | Default | Description |
|---|---|---|
days |
30 |
Number of days to aggregate (7, 14, 30, 60, 90) |
Response includes: summary KPIs, per-label totals with growth, daily timeline, country distribution, browser/OS/device breakdowns, hourly activity, and referrer ranking.
{
"status": "ok",
"totalVisits": 1420,
"totalLabels": 5,
"timestamp": "2026年02月11日T20:00:00.000Z"
}Badges follow the shields.io standard and support extensive customization.
| Parameter | Default | Description |
|---|---|---|
label |
PixelPulse |
Left-side text. Set to empty string for message-only badges |
message |
(visit count) | Right-side text. Overrides the dynamic counter |
color |
blue |
Right-side color: named (green, red, cyan...) or hex (#4c1, ff6600) |
labelColor |
#555 |
Left-side background color |
style |
flat |
Badge style: flat or flat-square |
logo |
— | Simple Icons slug (e.g. typescript, github, docker) |
logoColor |
— | Logo color override |
link |
— | URL to wrap the badge in a clickable link |
preview |
false |
Set to true to skip visit registration |
All settings are managed through environment variables. See .env.example for the full template.
| Variable | Default | Description |
|---|---|---|
PORT |
3001 |
Backend server port |
NODE_ENV |
development |
development / production |
LOG_LEVEL |
info |
Pino log level (debug, info, warn, error) |
API_KEY |
(empty) | Bearer token for /api/* routes — leave empty to disable auth |
DATABASE_URL |
file:./dev.db |
SQLite database path |
IP_SALT |
change-me |
Fixed salt for IP hashing — change this |
Browser/Bot PixelPulse Backend SQLite
│ │ │
│ GET /pixel/label.gif │ │
│ GET /pixel/label.svg │ │
│ GET /badge/label.svg │ │
│─────────────────────────────>│ │
│ 200 OK (image response) │ │
│<─────────────────────────────│ │
│ │ async (fire-and-forget) │
│ │ hash IP + geolocate │
│ │ parse UA + detect bot │
│ │ INSERT visit │
│ │───────────────────────────>│
│ │ │
Dashboard (React) PixelPulse Backend SQLite
│ │ │
│ GET /api/stats │ │
│ GET /api/analytics │ │
│─────────────────────────────>│ │
│ │ SELECT + aggregate │
│ │<───────────────────────────│
│ 200 JSON │ │
│<─────────────────────────────│ │
How it works:
- An image request (
/pixel/*or/badge/*) arrives from any surface — README, website, email client. - The server responds immediately with the image — zero latency added to the page load.
- Asynchronously (fire-and-forget): the IP is hashed, geolocation and User-Agent are parsed, bot detection runs, and the visit is inserted into SQLite.
- The dashboard reads aggregated data through the
/api/statsand/api/analyticsendpoints.
Project structure:
pixel-pulse/
├── backend/ # Express API + pixel/badge serving
│ ├── src/
│ │ ├── config/ # Environment validation
│ │ ├── lib/ # Core logic (visit, geo, ua, badge, logo)
│ │ └── routes/ # Pixel, badge, stats, analytics, health
│ └── prisma/ # Schema + SQLite database
└── frontend/ # React dashboard (Vite + TailwindCSS)
└── src/
├── api/ # API client + types
├── components/ # Shared UI (KpiCard, BadgeCreator, etc.)
├── context/ # Badge preset context
├── lib/ # Icons, presets, country codes
└── pages/ # Dashboard, Analytics, LabelDetail, Login
- IPs are never stored — only a SHA-256 hash with a configurable salt
- The salt is fixed (set in
.env), allowing consistent unique visitor counting across sessions - Geolocation is 100% local — uses the bundled MaxMind database, no external API calls
- No cookies, no fingerprinting, no third-party scripts
- All data stays in a single SQLite file on your machine
- You own everything — self-hosted, no data leaves your server
Distributed under the MIT License. See LICENSE for details.