RepoPilotOpen in app →

anuraghazra/github-readme-stats

:zap: Dynamically generated stats for your github readmes

GO

Healthy across the board

  • Last commit 5w ago
  • 5 active contributors
  • Distributed ownership (top contributor 45%)
  • MIT licensed
  • CI configured
  • Tests present
  • Small team — 5 top contributors

Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests

Embed this verdict

[![RepoPilot: GO](https://repopilot.app/api/badge/anuraghazra/github-readme-stats)](https://repopilot.app/r/anuraghazra/github-readme-stats)

Paste into your README — the badge live-updates from the latest cached analysis.

Onboarding doc

Onboarding: anuraghazra/github-readme-stats

Generated by RepoPilot · 2026-05-04 · Source

Verdict

GO — Healthy across the board

  • Last commit 5w ago
  • 5 active contributors
  • Distributed ownership (top contributor 45%)
  • MIT licensed
  • CI configured
  • Tests present
  • ⚠ Small team — 5 top contributors

<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>

TL;DR

github-readme-stats is a serverless Node.js application deployed on Vercel that dynamically generates SVG stat cards for GitHub profiles. It fetches data from the GitHub GraphQL API and renders customizable SVG cards showing user stats, top languages, pinned repos, Wakatime coding time, and GitHub Gist info — all embeddable in any Markdown README via a simple URL. The project is a flat serverless architecture: api/ contains the Vercel serverless function entry points (index.js for stats, pin.js for repos, top-langs.js, wakatime.js, gist.js), while src/ (referenced in package.json main: src/index.js) contains the core rendering logic, fetchers, and card generators. Scripts in scripts/ handle theme documentation generation, preview, and maintenance automation.

Who it's for

GitHub users (primarily developers) who want to showcase their coding activity and profile stats in their GitHub README.md files without manually updating images. Contributors are typically JavaScript/Node.js developers familiar with SVG rendering, Vercel serverless functions, and GitHub's GraphQL API.

Maturity & risk

The project is highly mature and production-ready — it has a robust CI/CD pipeline via GitHub Actions (.github/workflows/test.yml, e2e-test.yml, codeql-analysis.yml), Jest-based unit and E2E tests, CodeCov integration, and OpenSSF security scorecard. It is actively maintained with workflows for dependency updates (dependabot.yml) and automated theme PR management, indicating continuous development rather than abandonment.

The primary risk is single-maintainer dependency (anuraghazra), though CODEOWNERS and an active contributor base mitigate this somewhat. The project relies on GitHub's GraphQL API rate limits and PAT token availability, which are externally controlled. Vercel's free tier deployment limits and GitHub API changes (breaking GraphQL schema updates) could cause outages for self-hosters without warning.

Active areas of work

Active workflows include automated theme PR closing (stale-theme-pr-closer.yml, theme-prs-closer.yml), language JSON generation (update-langs.yml), and deploy preparation (deploy-prep.yml). The presence of jest.bench.config.js suggests recent or ongoing performance benchmarking work. Dependabot is configured for automated dependency updates.

Get running

git clone https://github.com/anuraghazra/github-readme-stats.git cd github-readme-stats npm install

Set up environment variables (see hiddenTraps)

cp .env.example .env # if available, else create .env manually

Run tests

npm test

Run locally via express wrapper

node express.js

Daily commands: npm test # Run Jest unit tests with coverage npm run test:watch # Watch mode npm run test:e2e # End-to-end tests (jest.e2e.config.js) npm run bench # Performance benchmarks node express.js # Local dev server (wraps api/ handlers) npm run lint # ESLint check npm run format # Prettier format

Map of the codebase

  • src/common/Card.js — Base card abstraction that all card types extend — defines SVG layout, theming, animations, and accessibility attributes used across every rendered card.
  • src/cards/stats.js — Core stats card renderer that transforms GitHub user data into SVG, demonstrating the canonical pattern for building a new card type.
  • src/fetchers/stats.js — Primary GitHub GraphQL fetcher with retry logic that queries stars, commits, PRs, and issues — the main data pipeline entry point.
  • api/index.js — Vercel serverless entry point for the stats card endpoint; shows how query params are parsed, validated, and dispatched to fetchers and renderers.
  • src/common/retryer.js — Implements token rotation and retry logic for GitHub API calls; critical for understanding rate-limit handling across all fetchers.
  • src/common/cache.js — Cache-Control header utility that enforces edge caching strategy on Vercel CDN — controls freshness of all card responses.
  • src/calculateRank.js — Implements the statistical rank calculation (S/A+/A/B/C) algorithm based on user activity — core business logic referenced by the stats card.

How to make changes

Add a new card type (e.g. a Contributions Heatmap card)

  1. Create a new fetcher module that queries the relevant GitHub GraphQL or REST endpoint, using retryer for token rotation and returning a normalized data object. (src/fetchers/contributions.js)
  2. Create a new renderer module that imports Card from src/common/Card.js, accepts the normalized data and options, constructs SVG elements, and returns the full SVG string. (src/cards/contributions.js)
  3. Create a new Vercel serverless function that parses query params (username, theme, locale, etc.), calls your fetcher, passes data to your renderer, sets cache headers via src/common/cache.js, and writes the SVG response. (api/contributions.js)
  4. Export the new fetcher and renderer from the barrel index so they are accessible project-wide. (src/cards/index.js)

Add a new theme

  1. Add a new theme entry to the themes object (following existing entries) with keys: title_color, icon_color, text_color, bg_color, border_color. (themes/index.js)
  2. Run the theme documentation generator script to regenerate the theme preview table in the README automatically. (scripts/generate-theme-doc.js)

Add a new i18n locale to cards

  1. Add a new locale key and all required translation strings to the translations map (following the existing en, cn, de pattern). (src/translations.js)
  2. Verify the I18n helper resolves the new locale key correctly and falls back to English when strings are missing. (src/common/I18n.js)
  3. Pass the locale query param through the API handler to the card renderer so the new locale can be selected by end users. (api/index.js)

Add a new stat row to the stats card

  1. Extend the GraphQL query or REST call in the stats fetcher to retrieve the new data point (e.g. discussions answered), and include it in the returned data object. (src/fetchers/stats.js)
  2. Add the new stat row to the card renderer: create a StatItem SVG element following the existing stars, commits, prs pattern, including icon reference from src/common/icons.js. (src/cards/stats.js)
  3. Add the display label string for the new stat to all supported locales. (src/translations.js)

Why these technologies

  • Vercel Serverless Functions — Zero-ops deployment with automatic edge caching via Cache-Control headers; each card type maps to a single function file requiring no server management.
  • GitHub GraphQL API — Allows fetching exactly the required fields (stars, commits, PRs, issues, followers) in a single round-trip, reducing latency vs multiple REST calls.
  • SVG (no frontend framework) — Cards are embedded in GitHub Markdown via <img> tags which cannot execute JavaScript; pure SVG with CSS animations is the only viable rendering target.
  • Node.js ESM modules — Modern module system aligns with Vercel's Node runtime; enables clean tree-shaking and top-level await in scripts.
  • Jest for testing — Snapshot testing of SVG output is idiomatic in Jest; mocking GitHub API responses is straightforward with Jest's module mocking system.

Trade-offs already made

  • Vercel edge Cache-Control instead of a database or Redis cache

    • Why: Eliminates infrastructure cost and complexity; CDN caching is sufficient for public, read-only SVG assets.
    • Consequence: Cache invalidation is time-based only (TTL); a user's stats won't update until the CDN TTL expires regardless of GitHub activity.
  • PAT token pool rotation in retryer.js instead of GitHub App authentication

    • Why: Simpler to set up for self-hosters; no OAuth app registration required.
    • Consequence: Each PAT has independent rate limits (5000 req/hr); pool exhaustion still causes failures under very high traffic.
  • Pure SVG string rendering instead of a headless browser (Puppeteer/Playwright)

    • Why: undefined
    • Consequence: undefined

Traps & gotchas

  1. You MUST set a GITHUB_TOKEN (Personal Access Token) environment variable — without it, all API calls fail immediately due to GitHub API authentication requirements. For local dev via express.js, create a .env file with GITHUB_TOKEN=your_pat. 2. The project uses ESM ('type': 'module' in package.json), so Jest requires the --experimental-vm-modules flag (already in npm scripts but can trip up direct jest invocations). 3. Node version is pinned via .nvmrc — use 'nvm use' before installing to avoid subtle ESM/Jest compatibility issues. 4. Vercel deployment requires configuring multiple PAT tokens (PAT_1, PAT_2, etc.) for rate limit distribution — a single token will hit limits quickly in production.

Architecture

Concepts to learn

  • Vercel Serverless Functions — Every api/*.js file IS a Vercel serverless function — understanding how Vercel routes requests, handles cold starts, and manages environment variables is essential to understanding the deployment model.
  • GitHub GraphQL API — All stat fetching uses GitHub's GraphQL API (not REST) — understanding GraphQL queries, pagination, and rate limiting explains why PAT tokens are required and how data is structured.
  • SVG Template Rendering — Cards are generated as raw SVG strings via JavaScript template literals — there is no React/Vue/DOM involved. Understanding SVG coordinate systems, viewBox, and text rendering is necessary to modify card layouts.
  • HTTP Cache-Control Headers — The cards use Cache-Control headers (s-maxage, stale-while-revalidate) to control Vercel's CDN caching behavior — this is how the service stays fast at scale without hitting GitHub API limits on every request.
  • GitHub API Rate Limiting & PAT Token Rotation — api/status/pat-info.js and the multi-PAT setup exist specifically to work around GitHub's 5000 req/hour rate limit by rotating between multiple tokens — a critical operational concept for self-hosters.
  • Node.js ECMAScript Modules (ESM) — The repo uses 'type': 'module' making all .js files ESM — this affects how imports/exports work, why Jest needs --experimental-vm-modules, and why dynamic require() calls will break.
  • WCAG Color Contrast — The devDependency 'color-contrast-checker' indicates theme colors are validated against WCAG accessibility standards — new themes must pass contrast ratio requirements, not just look visually appealing.

Related repos

  • DenverCoder1/github-readme-streak-stats — Companion tool in the same ecosystem that generates GitHub contribution streak cards, commonly used alongside github-readme-stats in READMEs.
  • lowlighter/metrics — Close alternative that also generates GitHub profile metric SVG cards but offers a much wider set of plugins and self-hosted GitHub Actions deployment instead of Vercel.
  • anuraghazra/github-readme-stats — This is the canonical repo; forks like jonah-dev/github-readme-stats exist for self-hosted variants with custom modifications.
  • tandpfun/skill-icons — Ecosystem companion — developers who embed github-readme-stats in their READMEs commonly also use skill-icons to display technology badges.
  • athul/waka-readme — Alternative Wakatime stats integration for GitHub READMEs, solving the same problem as github-readme-stats' wakatime card (api/wakatime.js) but as a GitHub Action.

PR ideas

To work on one of these in Claude Code or Cursor, paste: Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.

Add unit tests for src/calculateRank.js edge cases

The calculateRank.js file is a core algorithm that computes the GitHub stats rank/score shown on every stats card. Edge cases like zero commits, extremely high star counts, or negative values likely lack coverage. Robust tests here prevent regressions when the ranking formula is tuned and give new contributors confidence to improve the algorithm.

  • [ ] Open src/calculateRank.js and document every input parameter and boundary condition (e.g. totalCommits=0, followers=0, prs=0, issues=0, stars=0)
  • [ ] Create tests/calculateRank.test.js (check if it exists already; if partial, extend it) with test cases for: all-zero inputs, single non-zero input, very large values (e.g. 1M stars), and verify the returned rank letter grade (S, A+, A, B, C) matches expected thresholds
  • [ ] Add a test asserting that rank level boundaries are monotonically correct (e.g. a user with more commits + stars always scores >= a user with fewer)
  • [ ] Run 'npm test -- --coverage' and confirm calculateRank.js reaches ≥95% branch coverage
  • [ ] Update jest.config.js coverageThreshold if needed to enforce this going forward

Split src/cards/top-languages.js into smaller focused modules

top-languages.js is one of the most complex cards, handling layout logic for normal, compact, donut, and pie chart modes, language filtering, and size calculations all in one file. This makes it hard to test individual layout strategies and slows onboarding. Splitting it mirrors how other large open-source card libraries separate layout renderers from data transformation.

  • [ ] Audit src/cards/top-languages.js and identify the distinct responsibilities: data normalization/filtering, layout renderers (normal, compact, donut, pie), and the main card assembly function
  • [ ] Create src/cards/top-languages/ as a directory; move layout-specific SVG generators into separate files: normal-layout.js, compact-layout.js, donut-layout.js, pie-layout.js
  • [ ] Extract language data transformation helpers (hide languages, size calculation, percentage math) into src/cards/top-languages/utils.js
  • [ ] Update src/cards/top-languages.js (or index.js) to import from the new sub-modules, keeping the public API identical so api/top-langs.js needs no changes
  • [ ] Update existing tests in tests/ that reference top-languages to import from the new paths, and add focused unit tests for each layout renderer in isolation
  • [ ] Verify all existing snapshot tests still pass with 'npm test:update:snapshot'

Add a GitHub Actions workflow to validate theme color contrast accessibility (WCAG AA)

The repo already has scripts/generate-theme-doc.js, preview-theme.js, and .github/workflows/preview-theme.yml and stale-theme-pr-closer.yml, but there is no automated check that enforces WCAG AA color contrast ratios for new themes submitted via PRs. The devDependency 'color-contrast-checker' is already installed, meaning the tooling exists but is not wired into CI. This would prevent inaccessible themes from being merged.

  • [ ] Create scripts/check-theme-contrast.js that imports themes from themes/index.js, iterates every theme, and uses the already-installed 'color-contrast-checker' package to assert that title_color, text_color, and icon_color each meet WCAG AA contrast ratio (≥4.5:1) against bg_color
  • [ ] Output a human-readable failure report listing the theme name, failing color pair, and actual contrast ratio to make PR feedback actionable
  • [ ] Add a 'check-theme-contrast' script entry in package.json pointing to the new script
  • [ ] Create .github/workflows/theme-contrast-check.yml that triggers on pull_request when files matching 'themes/**' change, runs '

Good first issues

  1. Add missing test coverage for api/status/pat-info.js — this file handles PAT token introspection but likely has no dedicated test file in tests/. 2. Add or improve JSDoc comments to the card renderer functions in src/cards/ — the codebase is JavaScript without TypeScript, so inline documentation is the primary type guidance for contributors. 3. The api/gist.js endpoint is the newest card type — audit its error handling and edge cases against the more mature api/index.js to find gaps in validation or error messaging.

Top contributors

Recent commits

  • 5df91f9 — docs: add github action as recommended usage method (#4716) (rickstaa)
  • 8108ba1 — build(deps-dev): Bump @actions/core from 1.11.1 to 2.0.1 (#4702) (dependabot[bot])
  • 7d8dad0 — build(deps-dev): Bump eslint-plugin-jsdoc from 61.4.2 to 61.5.0 (#4688) (dependabot[bot])
  • a92b817 — build(deps-dev): Bump eslint from 9.39.1 to 9.39.2 (#4704) (dependabot[bot])
  • 903588b — build(deps-dev): Bump @eslint/js from 9.39.1 to 9.39.2 (#4703) (dependabot[bot])
  • 6ade6b4 — ci: revert restoring e2e test workflow (#4709) (opheliagoldstein)
  • 696dd6c — ci: restore e2e test workflow (#4708) (opheliagoldstein)
  • 61b9adb — ci(deps): Bump peter-evans/create-pull-request from 7.0.9 to 8.0.0 (#4706) (dependabot[bot])
  • 8ee077b — ci(deps): Bump actions/upload-artifact from 5.0.0 to 6.0.0 (#4705) (dependabot[bot])
  • a3f234a — ci(deps): Bump actions/setup-node from 6.0.0 to 6.1.0 (#4689) (dependabot[bot])

Security observations

  • High · Potential XSS via SVG/HTML Injection in Card Rendering — src/cards/stats.js, src/cards/repo.js, src/cards/top-languages.js, src/cards/wakatime.js, src/cards/gist.js, src/common/html.js, src/common/render.js. The project dynamically generates SVG content using user-supplied query parameters (e.g., username, theme, title, custom_title, etc.). If these values are not properly sanitized before being interpolated into SVG/HTML templates, an attacker could inject malicious SVG or HTML payloads. Files like src/common/html.js, src/common/render.js, and the card files (src/cards/*.js) are primary concerns. SVG supports <script> tags and event handlers (onload, onerror), making XSS plausible if user input reaches SVG output unescaped. Fix: Ensure all user-supplied input (query parameters) is strictly validated and HTML/SVG-entity-escaped before interpolation into SVG templates. Use a whitelist approach for parameters like theme, title, and username. Consider using a dedicated sanitization library. Validate that no raw string interpolation of untrusted data occurs in template literals producing SVG output.
  • High · PAT (Personal Access Token) Exposure Risk via Status Endpoint — api/status/pat-info.js. The file api/status/pat-info.js strongly suggests an endpoint that exposes information about GitHub Personal Access Tokens (PATs). If this endpoint leaks token metadata, scopes, validity, or — worse — partial token values, it could assist attackers in token abuse or enumeration. Even exposing which PATs are active/inactive could aid targeted attacks. Fix: Restrict access to the pat-info endpoint strictly (e.g., require authentication, limit to internal/admin use, or remove the endpoint entirely). Ensure no token values or sensitive metadata are exposed in responses. Audit the endpoint to confirm it only returns minimal, non-sensitive information absolutely necessary for operations.
  • High · GitHub Token Stored in Environment Variables Without Rotation Strategy — src/common/envs.js, src/common/retryer.js, api/status/pat-info.js. The application relies on GitHub PATs stored in environment variables (referenced in src/common/envs.js) to make authenticated GitHub API requests. If these environment variables are misconfigured, logged, or exposed via error messages or the pat-info endpoint, they could be leaked. Multiple PATs (PAT_1, PAT_2, etc.) are cycled via a retryer pattern, suggesting several high-privilege tokens are in use simultaneously, increasing the blast radius if any one is compromised. Fix: Ensure PATs have the minimum required scopes (read-only, public data only). Implement regular automated token rotation. Ensure tokens are never logged or included in error responses. Use secret scanning tools to detect accidental exposure. Consider using GitHub Apps instead of PATs for better security controls.
  • Medium · Missing HTTP Security Headers — api/index.js, api/pin.js, api/top-langs.js, api/wakatime.js, api/gist.js. The API endpoints (api/index.js, api/pin.js, api/top-langs.js, api/wakatime.js, api/gist.js) serve SVG and potentially HTML content. Without security headers such as Content-Security-Policy, X-Content-Type-Options, X-Frame-Options, and Cache-Control directives, browsers may be susceptible to MIME-sniffing, clickjacking, or caching sensitive responses. The SVG content-type in particular can execute scripts in some browser contexts. Fix: Add security headers to all API responses: set Content-Type to 'image/svg+xml', add 'X-Content-Type-Options: nosniff', consider 'Content-Security-Policy: default-src none' for SVG responses, and set appropriate Cache-Control headers. For SVG served in <img> tags, scripts are typically blocked by browsers, but explicit headers reinforce this.
  • Medium · Server-Side Request Forgery (SSRF) Risk via External API Calls — undefined. The application makes HTTP requests to the GitHub GraphQL API and potentially WakaTime API based on user-supplied usernames and other parameters. While these calls appear to go to fixed endpoints, if any URL construction involves user input (e.g., in src/common/http.js or fetcher files), there is a potential SSRF risk. The wakatime fetcher (api/wakatime.js Fix: undefined

LLM-derived; treat as a starting point, not a security audit.

Where to read next


Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.

GO · anuraghazra/github-readme-stats — RepoPilot Verdict