Generate SVG cards summarizing a GitHub user's profile — written in Go.
ghstats is a single-binary CLI (and a GitHub Action wrapping it) that fetches
data for a GitHub user and writes a themed set of SVGs you can embed in your
profile README.
Marketplace listing: ghstats-cards · Source: tiennm99/ghstats
Cards rendered:
| # | Card | What it shows |
|---|---|---|
| 0 | Profile details | Login (Name) title + Octicon-labelled rows for company, location, link, join date (with age), followers/following, public repos |
| 1 | Repos per language | Donut + legend: how many owned non-fork repos use each language as primary |
| 2 | Most commit language (last year) | Donut + legend: last-year commits byte-weighted across each repo's language breakdown |
| 3 | Stats | Star, commit (lifetime + last-year), PR, issue, PR-review, contributed-to totals |
| 4 | Productive time (last year) | 24-hour bar chart with axes, title includes UTC±N.NN |
| 5 | Productive weekday (last year) | 7-bar day-of-week chart, peak day highlighted |
| 6 | Contributions (last year) | Smooth monthly area chart, Y-axis mirrored both sides, mm/yy labels |
| 7 | Contributions heatmap | Classic 7×53 calendar grid with theme-derived intensity ramp and legend |
| 8 | Top starred repos | Top 5 owned non-fork repos by ⭐, language dot + proportional bar |
| 9 | Streak | Current streak, longest streak, active days / total days with date ranges |
| 10 | Most commit language (all time) | Same as #2 but over lifetime commits |
| 11 | Productive time (all time) | Same as #4 but over lifetime commits |
| 12 | Productive weekday (all time) | Same as #5 but over lifetime commits |
| 13 | Contributions (all time) | Area chart across every active year, auto-thinned x-axis labels |
| 14 | Contributions by year | One bar per active year, peak year highlighted |
Live render against the author's profile, committed by .github/workflows/demo.yml on every push to main. Rendered with start_of_week: monday so the heatmap rows and weekday bars start on Mon. Other 64 themes in the demo gallery.
- tiennm99/tiennm99 — author's profile README, refreshed daily via
tiennm99/ghstats@v1. Two-per-row layout, dracula theme.
Drop this in .github/workflows/ghstats.yml in your profile repo (the one
named after your username):
name: ghstats
on:
schedule:
- cron: "0 0 * * *" # daily
workflow_dispatch:
permissions:
contents: write
jobs:
cards:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: tiennm99/ghstats@v1
with:
user: ${{ github.repository_owner }}
token: ${{ secrets.GHSTATS_TOKEN }} # classic PAT with read:user + repo
themes: dracula,github_dark,tokyonight
tz: Asia/Saigon
include_forks: "true"
include_private: "true"
commit_changes: "true"Then embed the cards in your README.md:














| Input | Default | Description |
|---|---|---|
user |
— | GitHub username (required) |
token |
${{ github.token }} |
PAT with read:user + repo for private repo stats |
out |
output |
Output directory |
themes |
dracula |
Comma-separated theme ids, or all |
tz |
UTC |
IANA tz for the productive-time card (e.g. Asia/Saigon) |
start_of_week |
sunday |
First day of week for heatmap rows and weekday bars (sunday…saturday) |
top_repos |
0 |
Optional cap on seed repos probed for commit history (0 = unlimited) |
commits_per_repo |
500 |
Max commits sampled per repo (covers last-year and all-time aggregates) |
include_forks |
true |
Include forked repos in stats and commit probing |
include_private |
true |
Include private repos (requires PAT with repo scope; silently no-op otherwise) |
commit_changes |
false |
Commit generated cards back to the repo |
commit_message |
chore: update ghstats cards |
Commit message |
commit_branch |
(current ref) | Target branch for auto-commit |
author_name |
github-actions[bot] |
Commit author |
author_email |
…@users.noreply.github.com |
Commit email |
go install github.com/tiennm99/ghstats@latestOr build from source:
git clone https://github.com/tiennm99/ghstats
cd ghstats
go build -o ghstats .Then:
export GITHUB_TOKEN=ghp_xxx
ghstats -user tiennm99 -themes dracula,github_dark -tz Asia/Saigon -out output| Flag | Default | Description |
|---|---|---|
-user |
(required) | GitHub username |
-token |
$GITHUB_TOKEN |
Personal access token |
-out |
output |
Output directory (<out>/<theme>/…svg) |
-themes |
dracula |
Comma-separated theme ids, or all |
-tz |
Local |
IANA timezone for productive-time cards |
-start-of-week |
sunday |
First day of week for heatmap rows and weekday bars (sunday…saturday) |
-top-repos |
0 |
Optional cap on seed repos probed (0 = unlimited) |
-commits-per-repo |
500 |
Max commits sampled per repo |
-include-forks |
true |
Include forked repos in the stats |
-include-private |
true |
Include private repos (requires repo PAT scope; silently no-op otherwise) |
-list-themes |
Print available theme ids and exit |
Repo sampling uses a seed list built from contributionsCollection.commitContributionsByRepository, unioned across every active contribution year. This catches every repo you've committed in — not just your top-starred ones.
Commit-to-language is byte-weighted: each commit credits every language in the repo, proportional to linguist's byte share. A commit to a 60% Go / 40% Python repo adds 0.6 to Go and 0.4 to Python, regardless of which file was touched. Caveats:
- Linguist excludes prose (Markdown, AsciiDoc, reST) from byte counts, so heavily-Markdown repos skew toward whatever small code fraction linguist did detect.
- For per-file accuracy, a future
-accurate-languagesmode is planned (per-commit REST + go-enry).
Cost per run (current defaults, typical user):
- ~1 profile query + ~1 query per active year + ~50 commit-history pages ≈ 50-70 GraphQL calls.
- Zero REST calls. Well under the 5000 points/hr budget.
Run ghstats -list-themes for the full list (65 themes ported from
github-profile-summary-cards). Built-ins include default, dark, dracula,
github, github_dark, tokyonight, onedark, nord_dark, nord_bright,
gruvbox, radical, synthwave, monokai, solarized, solarized_dark,
transparent, and more. Preview every one against real profile data in the
demo gallery.
output/
dracula/
profile-details.svg
repos-per-language.svg
most-commit-language.svg
stats.svg
productive-time.svg
productive-weekday.svg
contributions.svg
contributions-heatmap.svg
top-starred-repos.svg
streak.svg
most-commit-language-all-time.svg
productive-time-all-time.svg
productive-weekday-all-time.svg
contributions-all-time.svg
contributions-by-year.svg
output/ is entirely gitignored — it's regenerated on each run. For a
reference render of every card in every theme, see the CI-built
demo/ gallery instead.
The default ${{ github.token }} can read public user data but will not see
your private-repo commits. For accurate stats, create a classic personal
access token with read:user and repo, save it as a repo secret (e.g.
GHSTATS_TOKEN), and pass it via the token input. include_private
defaults to true so those commits are counted automatically once the token
has repo scope; pass include_private: "false" if you want to keep private
work out of the rendered cards even when the token can see it.
- github-profile-summary-cards by @vn7n24fzkq — card layout, chart styles, theme palette, Octicon selection, and output structure.
Apache-2.0 — see LICENSE.