Waka Personal is a self-hosted, single-user WakaTime-compatible backend with a built-in dashboard. It lets you keep using WakaTime clients and plugins, store the data in PostgreSQL, and browse your own coding activity from a local web UI.
Good fit if you want to:
- keep your coding history in your own database
- point WakaTime-compatible plugins at your own API
- run a personal dashboard on localhost or your own server
- import old backup exports and keep everything in one place
- WakaTime-compatible ingest and query endpoints for heartbeats, durations, summaries, stats, status bar, and file experts
- Built-in Astro + React dashboard, served by the Go app from
dist/ - PostgreSQL storage with
goosemigrations - Backup importer for
.jsonand.json.gzexports - Optional API key auth for a single-user setup
- Health endpoints, rate limiting, and security headers out of the box
- Backend: Go 1.26, Fiber v2, pgx/v5, goose, zerolog
- Frontend: Astro, React, Tailwind CSS
- Database: PostgreSQL
cmd/ app entrypoints
internal/http/ Fiber handlers and transport concerns
internal/service/ business logic
internal/store/ PostgreSQL access and migrations
src/ Astro + React dashboard
db/migrations/ goose migration files
- Copy
.env.exampleto.envand adjust values. - Start PostgreSQL:
docker compose up -d postgresSet DATABASE_URL in .env to match the Docker database name if you use the bundled Compose file:
DATABASE_URL=postgres://postgres:postgres@localhost:5432/dvgamerr?sslmode=disable- Install frontend dependencies and build the dashboard:
bun install
bun run build:dist- Start the API:
go run ./cmdThe API applies pending goose migrations on startup.
If dist/index.html exists, the Go server also serves the dashboard from the same origin.
POST /api/v1/users/current/heartbeatsPOST /api/v1/users/current/heartbeats.bulkGET /api/v1/users/current/heartbeats?date=YYYY-MM-DDDELETE /api/v1/users/current/heartbeats.bulkGET /api/v1/users/current/durationsGET /api/v1/users/current/summariesGET /api/v1/users/current/statsGET /api/v1/users/current/statusbar/todayPOST /api/v1/users/current/file_expertsGET /api/v2/dashboard(dashboard aggregate endpoint, no auth)GET /api/v2/live(live dashboard slice, no auth, used by the web UI every 60 seconds)GET /api/v2/insights(insights page data, no auth)GET /api/v2/wrapped(wrapped page data, no auth)
{
"wakatime.apiKey": "your-local-api-key",
"wakatime.apiUrl": "http://localhost:8080/api/v1"
}[settings]
api_url = http://localhost:8080/api/v1
api_key = <APP_API_KEY>
debug = falseThis is useful when you want to keep a default WakaTime setup, but override some traffic to Wakapi or your local Waka Personal instance:
[settings]
api_url = https://api.wakatime.com/api/v1
api_key = <api_key_waka>
debug = false
[api_urls]
.* = https://wakapi.dev/api|<api_key_wakapi>
.* = http://localhost:8080/api/v1|<APP_API_KEY>Use the same value for APP_API_KEY in .env and in your client config.
WakaTime plugins send the API key as the Basic-auth username, and this server only checks that the received value matches APP_API_KEY.
If the frontend and backend share the same origin, leave PUBLIC_API_BASE empty.
Use these variables when building the dashboard:
PUBLIC_API_BASEfor the API origin when frontend and backend are on different originsPUBLIC_APP_TIMEZONEfor dashboard queries
The built-in dashboard uses GET /api/v2/dashboard for the main aggregate and polls GET /api/v2/live every 60 seconds for live widgets. /insights and /wrapped load from GET /api/v2/insights and GET /api/v2/wrapped. These web UI endpoints skip API key auth.
Supports backup files in .json and .json.gz format with top-level user, range, and days[].heartbeats[].
go run ./cmd/importer --file E:\path\to\backup.jsonOptional flags:
--format backup-json--dry-run
On Windows, building or running the DuckDB-backed importer from source requires CGO_ENABLED=1 and a GCC toolchain such as MSYS2 UCRT64.
go run ./cmd/migrate up
go run ./cmd/migrate status
go run ./cmd/migrate down
go run ./cmd/migrate create add_new_table sql
go run ./cmd/migrate fix
go run ./cmd/migrate validateMigration config comes from .env, including DATABASE_URL, MIGRATION_DIR, and GOOSE_TABLE.
go run ./cmd
go run ./cmd/migrate up
go run ./cmd/importer --file <path-to-backup.json>
go test ./...
bun run build:distOn Windows, avoid rebuilding over the same running .exe path. go build -o bin/waka.exe ./cmd/main.go will fail while bin/waka.exe is still running because Windows locks the file.
Use one of these instead:
go run ./cmd
.\scripts\dev-api.ps1
.\scripts\dev-api.ps1 -NoRun.\scripts\dev-api.ps1 builds a timestamped bin/waka-dev-*.exe, removes old stopped dev binaries, and runs the new build without colliding with the previous process.
- Auth is bypassed when
APP_API_KEYis empty, so set it in production. - The default config database name is
waka_personal, butdocker-compose.ymlusesdvgamerr. - When changing heartbeat persistence or import behavior, check both the live ingest path and the importer path.