342490035b36958e9ef8f8c55485950a26b2ca43
git-contribution-graph
A self-hosted Symfony service that merges contribution data from GitHub, GitLab and Gitea into a single GitHub-style heatmap SVG you can embed anywhere.
https://your-host/graph.svg?github_user=you&github_token=ghp_…&gitea_user=you&gitea_token=…&gitea_url=https://git.example.com
Features
- Three platforms — GitHub, GitLab (cloud or self-hosted), Gitea (self-hosted)
- Merged heatmap — contributions from all sources are summed per day
- GitHub colour palette — exact dark/light theme tokens
- Embeddable — returns
image/svg+xml, works in any<img>tag or Markdown - Cached — responses cached for 1 hour, safe to embed in public READMEs
- Graceful degradation — if one platform fails, the others still render
Deploy
Requirements
- Docker + Docker Compose
1. Clone and configure
git clone https://git.arthurerlich.de/haylan/git-contribution-graph.git
cd git-contribution-graph
cp .env .env.local
Edit .env.local:
APP_ENV=prod
APP_DEBUG=0
APP_SECRET=<generate with: openssl rand -hex 16>
2. Start
docker compose up -d
The service listens on port 8080 by default. Put Traefik or nginx in front of it for HTTPS.
3. Health check
GET /health → {"status":"ok"}
API
GET /graph.svg
| Parameter | Required | Description |
|---|---|---|
github_user |
✗ | GitHub username |
github_token |
✗ | GitHub PAT — scope: read:user |
gitlab_user |
✗ | GitLab username |
gitlab_token |
✗ | GitLab PAT — scope: read_user, read_api |
gitlab_url |
✗ | GitLab base URL (default: https://gitlab.com) |
gitea_user |
✗ | Gitea username |
gitea_token |
✗ | Gitea PAT — scope: read:user |
gitea_url |
✗ | Gitea instance URL, e.g. https://git.example.com |
theme |
✗ | dark (default) or light |
All platform parameters are optional — include only the ones you use. At least one platform must be configured for a non-empty graph.
Embedding in a README
GitHub-only

GitHub + Gitea

All three platforms

Dark vs Light theme
<!-- Dark (default) -->

<!-- Light -->

Security note: API tokens embedded in public Markdown URLs are visible to anyone. Use read-only fine-grained tokens with minimal scopes. For private repos or sensitive setups, proxy the request server-side and keep tokens in environment variables.
Token setup
GitHub
- Go to Settings → Developer settings → Personal access tokens → Fine-grained tokens
- Set Resource owner to your account
- Under Permissions → Account permissions, set Contribution activity → Read-only
- Generate and copy the token
GitLab
- Go to User Settings → Access Tokens
- Add a token with scopes:
read_user,read_api - Generate and copy the token
Gitea
- Go to Settings → Applications → Manage Access Tokens
- Add a token with permission: user → Read
- Generate and copy the token
Development
# Install deps
composer install
# Run dev server
APP_ENV=dev php -S localhost:8080 -t public
# Test endpoint
curl "http://localhost:8080/graph.svg?gitea_user=haylan&gitea_token=YOUR_TOKEN&gitea_url=https://git.arthurerlich.de" -o graph.svg
Architecture
GET /graph.svg
│
├─ GitHubProvider → GitHub GraphQL API (contributionCalendar)
├─ GitLabProvider → GitLab REST API (/users/:id/events)
└─ GiteaProvider → Gitea REST API (/users/:user/heatmap)
│
merge by date (sum counts)
│
SvgRenderer
│
image/svg+xml (cached 1h)
License
MIT
Description
Languages
PHP
98.2%
Dockerfile
1.8%