resume.github.com is a small, single-page web app that turns any public GitHub
profile into a printable, share-friendly résumé. It runs entirely in the
browser: a tiny JavaScript file pulls public data from the GitHub REST API,
fills in a Mustache template, and renders the result as a styled CV that
prints cleanly to PDF or paper.
This guide is written for the end user — the person who wants to generate their own résumé page or look up someone else's. It covers the hosted site (no install required), the optional self-hosted setup, and every visible behaviour of the front-end. For internals, deployment, and security notes, see the companion Administrator & Developer Guide in the same folder.
Note
The repository is dormant — the
f599075commit this guide is pinned to dates from August 2016. The site itself (https://resume.github.com) still works because it depends only on the public GitHub REST API, jQuery, and Mustache. There is no backend, no database, and no account to create.
1. Quick Start
The fastest way to use this project is to visit the hosted site — there is nothing to install, and no account to create.
Generate your résumé in 30 seconds
- Open the hosted site:
https://resume.github.com. - Sign in to GitHub if you aren't already, and star the
resume/resume.github.comrepository. This is the project's opt-in mechanism — without a star on that repo, the app will refuse to render your profile. - Append your GitHub username as a query string:
https://resume.github.com/?yourusername. - Wait a moment while the page fetches your profile, repos, merged pull
requests, and organisations from
api.github.com. - When the résumé renders, click the Print button at the top of the
page (or use your browser's
Ctrl+P/Cmd+P) to save it as a PDF or send it to a physical printer.
Tip
The query-string syntax is unusual — there is no
?username=key, just the bare value:?octocat,?defunkt,?torvalds. The first key fromwindow.location.searchis read as the username regardless of its name, so?u=octocatand?octocat=anythingboth resolve to the useroctocatorurespectively.
What the page actually shows
A rendered résumé pulls from your public GitHub data only and contains:
- Identity block — display name (falls back to login), avatar, location, email, blog URL, and join date. If you joined GitHub in the first four months (Feb–May 2008) the page tags you as an Early Adopter.
- Status label — a single line ranging from Inactive GitHub user to Passionate GitHub user, computed from a weighted score of your repositories, gists, followers, and following counts.
- Top languages — up to nine programming languages you publish in, ranked by repository count, displayed with a percentage of your total.
- Featured projects — up to five non-fork repositories ranked by
popularity (watchers + forks), with name, year span, language, and
description. Emoji shortcodes (
:rocket:) are rewritten to GitHub's 20×20 emoji images. - Open-source contributions — repositories where your merged pull requests live, ranked by the number of merged PRs you authored.
- Organisations — up to five organisations you belong to (subject to a shared item-count limit).
- Print button — calls
window.print()for a clean, paginated output.
Important
The site fetches data via JSONP (
?callback=?). That means the unauthenticated public GitHub API is used, with its global rate limit of 60 requests per hour per IP address. Multiple résumé loads from the same network can hit the limit and trip the API limit reached screen.
Looking up someone else
The same query-string mechanism works for any GitHub user or organisation.
Try https://resume.github.com/?defunkt to see one of GitHub's earliest
employees, or substitute any login. The look-up is read-only — no data
about the visitor is sent to GitHub beyond what the unauthenticated API
calls naturally include.
2. Installation
Most users do not need to install anything. The project is a public website, and the only "installation" required is starring the repository in your GitHub account so the opt-in check passes.
Path A — Use the hosted site (recommended)
| Step | Action | Where |
|---|---|---|
| 1 | Sign in to GitHub | https://github.com |
| 2 | Star resume/resume.github.com |
https://github.com/resume/resume.github.com (click the Star button) |
| 3 | Open your résumé | https://resume.github.com/?yourusername |
That's the entire installation. There is no package to download, no CLI to install, and no configuration file to edit.
Tip
If you ever want to opt out of being rendered, simply unstar the repository. The next time someone visits
https://resume.github.com/?yourusernamethey will see the opt out screen instead of your résumé.
Path B — Run a local copy
You only need to run the project locally if you want to embed it in your own site, customise the styling, or develop a fork. The repository is a plain static site plus a one-line Rack runner; any static file server works.
Prerequisites
- [x] A copy of the source:
git clone https://github.com/resume/resume.github.com.git - [x] One of: Ruby + Rack, Python, Node.js, or any other static file server
- [x] A modern browser with JavaScript enabled
Run with Rack (matches the upstream config.ru)
cd resume.github.com
gem install rack
rackup config.ru
[2026-04-29 10:00:00] INFO WEBrick 1.7.0
[2026-04-29 10:00:00] INFO ruby 3.2.2 (2023-03-30) [x86_64-linux]
[2026-04-29 10:00:00] INFO WEBrick::HTTPServer#start: pid=12345 port=9292
Then visit http://localhost:9292/?yourusername.
Run with Python's built-in server
cd resume.github.com
python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
Then visit http://localhost:8000/?yourusername.
Run with Node's http-server
cd resume.github.com
npx http-server -p 8080
Then visit http://localhost:8080/?yourusername.
Warning
If you load
index.htmldirectly viafile://(double-clicking the file in your file manager), the$.ajaxcalls that fetchviews/index.html,views/resume.html, etc. will be blocked by your browser's same-origin /file://policy. Always serve over HTTP, even for local development.
Verifying a local install
Once your server is running, verify each piece in turn:
curl -s http://localhost:9292/index.html | head -5
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Resume</title>
If the index page loads but no résumé appears when you append ?<username>,
open your browser's developer console — the front end logs JavaScript
errors via console.log(err) from inside the $(document).ready block.
3. Configuration
The repository ships with no configuration file, no environment
variables, and no command-line flags. Everything that the page does is
controlled by either the URL query string or hard-coded constants in
js/githubresume.js.
Runtime input — the URL
| Input | Where | What it does |
|---|---|---|
Bare query string (e.g. ?octocat) |
After ? in the URL |
Sets the GitHub username to render. The first key parsed from window.location.search becomes the value of username. Empty query routes to the home page. |
https:// vs http:// |
Page protocol | Determines whether Google Analytics loads from https://ssl.google-analytics.com or http://www.google-analytics.com. |
Note
The URL parser walks
window.location.searchonce and assigns the result tourlParams[0]. It does NOT support multiple parameters, named keys (?username=…), or fragments. Any extra&key=valuepairs are ignored.
Hard-coded knobs (require editing source)
If you self-host and want to tweak behaviour, these are the only values
worth knowing about. They live near the top of js/githubresume.js or
inside the run() function. See the Admin Guide for the full reference.
| Constant | Default | What it controls |
|---|---|---|
trackerId |
'UA-21222559-1' |
Google Analytics property. Set to falsy to disable analytics entirely. |
maxItems |
5 |
Maximum featured projects shown. Also reused as the soft cap for organisations. |
maxLanguages |
9 |
Maximum number of languages displayed in the Languages block. |
COEF_REPOS, COEF_GISTS, COEF_FOLLOWERS, COEF_FOLLOWING |
2, 0.25, 0.5, 0.25 |
Weighting coefficients for the user-status score. |
FIRST_STEP … FIFTH_STEP |
0, 5, 20, 50, 150 |
Score thresholds that map to the Inactive → Passionate labels. |
Tip
If you fork the project for your own portfolio, the very first edit almost everyone makes is replacing
trackerIdwith their own Google Analytics ID, or removing the analytics block at the bottom ofjs/githubresume.jsentirely.
Stylesheets
Two CSS files control the appearance:
css/resume.css— on-screen styling, layout, colours.css/print.css— applied via@media print, strips colour and chrome for clean PDF output.
You can edit these directly without touching JavaScript. The HTML view
fragments in views/*.html are Mustache templates rendered into a single
<div id="resume"> host element, so any CSS selector that targets a
template's class will style the rendered output.
4. Core Usage
This section walks through every visible behaviour and screen in the app, in the order a real visitor encounters them.
4.1 Landing page (no username)
When you load the site with no query string, the JavaScript falls into the
home() branch and AJAX-loads views/index.html into the #resume
container. This is the default landing experience — a brief introduction
plus an input field for the username.
4.2 Rendering a résumé
When the URL contains a query string, the run() function executes a
multi-step pipeline:
- Star check — synchronous
GET https://api.github.com/users/<user>/starred?per_page=100, paginated 100 at a time. The walker stops as soon as it seesresume/resume.github.cominrepo.full_name. If the user has not starred the repo,views/opt_out.htmlis shown. - User profile —
GET https://api.github.com/users/<user>?callback=?(JSONP). Populates the identity block, computes Early Adopter and the Status Label. - Repositories —
GET https://api.github.com/users/<user>/repos?per_page=100&callback=?, paginated. Forks are filtered out. The remainder are sorted bywatchers + forks, top five rendered into#jobsfromviews/job.html, and the language histogram populated. - Merged pull requests —
GET https://api.github.com/search/issues?q=type:pr+is:merged+author:<user>&per_page=100&callback=?. Results are bucketed byrepository_url, sorted by count, and rendered into#contrib-jobsfromviews/contrib.html. - Organisations —
GET https://api.github.com/users/<user>/orgs?callback=?. Up tomaxItemsorganisations are rendered into#orgsfromviews/org.html. - Title update — the document title becomes
<Name>'s Résumé. - Print binding — the
#actions #printelement is wired to callwindow.print().
Note
The user profile, repositories, merged PRs, and organisations calls all run in parallel as soon as the star check passes. Render order is therefore not deterministic — the first block that returns from GitHub populates first.
4.3 The status label
Your résumé header includes a single status string. It is derived from this formula:
score = (public_repos * 2.00)
+ (public_gists * 0.25)
+ (followers * 0.50)
+ (following * 0.25)
+ (early_adopter ? 1 : 0)
+ (location && email && blog ? 1 : 0)
The score then maps to a label:
| Score | Label |
|---|---|
0 |
Inactive GitHub user |
0 < s ≤ 5 |
Newbie GitHub user |
5 < s ≤ 20 |
Regular GitHub user |
20 < s ≤ 50 |
Advanced GitHub user |
50 < s ≤ 150 |
Enthusiastic GitHub user |
> 150 |
Passionate GitHub user |
The Early Adopter tag is awarded if your account was created in 2008
through May (or earlier — the original code includes a since <= '2007'
branch even though GitHub launched in February 2008).
4.4 Featured projects
The repos table renders five rows. Each row pulls from one of your
non-fork repositories, ranked by watchers + forks (so popularity, not
recency, drives the order). The shape of each row is:
| Field | Source |
|---|---|
| Name | repo.name |
| Date | repo.created_at year, optionally created – pushed if different |
| Language | repo.language |
| Description | repo.description (with :emoji: shortcodes rewritten to GitHub's emoji images) |
| Watchers | repo.watchers (label switches between star and stars) |
| Forks | repo.forks (label switches between fork and forks) |
| Homepage | repo.homepage |
Note
"Watchers" here is the legacy GitHub field — what's now called Stars. The label
'stars'is shown on rendered output, but the underlying JSON property iswatchers.
4.5 Languages bar
The languages histogram counts each non-fork repo's language field once,
sorts by frequency, takes the top nine, and renders each as
<a href="https://github.com/search?q=user:<name>&l=<encoded language>">…</a>
with a percentage of the total. Languages are grouped into rows of three
in the rendered HTML.
4.6 Open-source contributions
This block uses GitHub's search-issues endpoint to count merged PRs
authored by the user, grouped by repository. Each row links to the
/commits?author=<user> URL on the upstream project.
Warning
The search endpoint is heavily rate-limited (separate from the regular 60-per-hour limit) and search results are capped at 1000 items globally. Users with very large contribution histories may see incomplete contribution lists.
4.7 Organisations
The orgs section is rendered from views/org.html. It uses the legacy
/orgs endpoint without a per-page parameter and shares the itemCount
counter with the projects block — so if a user already has five featured
projects rendered, organisations are silently truncated.
4.8 Printing
The #print button at the top of the page calls window.print(). Modern
browsers will offer "Save as PDF" as a destination. The css/print.css
sheet handles the print-only layout (fewer colours, removal of
interactive chrome, page-friendly typography).
Tip
For the cleanest printable output, set your browser's print preview to No headers and footers and Default margins. Chrome and Edge both respect
@media printand@pagedirectives shipped inprint.css.
5. Common Tasks
A handful of recipes for things visitors and self-hosters routinely need.
5.1 Save your résumé as a PDF
- Visit
https://resume.github.com/?yourusernameand wait for the page to fully render. - Click the Print button or press
Ctrl+P(Cmd+Pon macOS). - In the print dialog, choose Save as PDF as the destination.
- Check Background graphics is enabled if you want the avatar and colour accents to survive the export.
- Save. Done.
5.2 Share your résumé as a link
There is nothing to share other than the URL itself. Send
https://resume.github.com/?yourusername in an email, in your LinkedIn
Featured section, or as a vCard URL. The page is fully public — no
authentication is required for the recipient.
Tip
If the recipient does not have a GitHub account, they can still view your résumé. The star check applies only to the subject of the résumé (the user being looked up), not the visitor.
5.3 Use the project for an organisation
Organisations and users share the same URL pattern:
https://resume.github.com/?orgname will render an organisation résumé
using the views/resumeOrgs.html template instead of the user template.
The decision is made at runtime based on data.type === 'Organization'
in the /users/<name> response.
5.4 Customise styling on a fork
- Fork
resume/resume.github.comto your own GitHub account. - Edit
css/resume.cssandcss/print.css— these contain all visible styling. - Push to your fork's
master(ormain) branch. - Enable GitHub Pages for your fork on the
masterbranch root. - Visit
https://<your-username>.github.io/resume.github.com/?<target-user>.
5.5 Embed in another site
Because the project is purely static and uses window.location.search
for input, embedding via <iframe> is straightforward:
<iframe
src="https://resume.github.com/?yourusername"
width="800"
height="1200"
style="border:0">
</iframe>
Note
The frame will inherit the parent page's print CSS only when the user right-clicks the iframe and chooses Print. A regular full-page print from the parent will not include the iframe content.
5.6 Disable Google Analytics on a fork
Open js/githubresume.js, find the var trackerId = 'UA-21222559-1';
line, and either delete it, comment it out, or set it to a falsy value.
The trailing analytics block will skip itself when trackerId is falsy.
6. Troubleshooting
Common failure modes, the cause behind each, and how to recover.
Error: page shows the Opt out screen instead of a résumé
Cause: The user being looked up has not starred the
resume/resume.github.com repository on GitHub, so the
github_user_starred_resume() check returned false.
Solution:
- Sign in to GitHub as the user whose résumé you want to display.
- Visit
https://github.com/resume/resume.github.com. - Click the Star button at the top right of the repo page.
- Reload
https://resume.github.com/?yourusernameand wait for the star to propagate to GitHub's API cache (usually instant).
Prevention: Star the repo as the very first step. Bookmark the star
URL: https://github.com/resume/resume.github.com.
Error: page shows the API limit reached screen
Cause: Your IP has hit GitHub's unauthenticated rate limit (60 requests per hour). The starred-repos check alone consumes one request per 100 starred repos; users with many stars can blow through the budget quickly.
Solution:
- Wait until the next hour rolls over (the limit is per UTC hour, not sliding).
- Switch to a different network — a mobile hotspot or VPN egress with a different IP often has fresh quota.
- If you self-host, see the Admin Guide for the unsupported pattern of adding personal access tokens (the upstream project does not authenticate any of its requests).
Prevention: Avoid reloading the page repeatedly during testing. Cache responses if forking.
Error: page shows the Not found screen
Cause: The ?username you supplied does not match any GitHub user or
organisation, so the /users/<name>/starred call returned HTTP 404.
Solution:
- Double-check the GitHub username for typos (case-insensitive but exact otherwise).
- Confirm the account still exists at
https://github.com/<username>. - If the account was renamed, use the new login.
Prevention: Use the exact login string from the user's GitHub URL
(https://github.com/<this-part>).
Error: blank page, console shows a $ is not defined or AJAX error
Cause: Either jQuery / Mustache failed to load, or you opened
index.html over the file:// protocol where AJAX is blocked.
Solution:
- Always serve the site over HTTP — see Section 2 for one-liner commands using Rack, Python, or Node.
- Open your browser's network tab and confirm the external scripts (jQuery, Mustache) are resolving without 404s.
- If on a corporate network, verify there's no proxy stripping third-party scripts.
Prevention: Use one of the supported local-dev paths in Section 2.
Error: Google Analytics throws a CSP violation in the console
Cause: Your hosting environment enforces a Content Security Policy that blocks Google Analytics scripts.
Solution:
- Edit
js/githubresume.jsand remove or comment out the trailingif (trackerId)block. - Or extend your CSP to allow
https://www.google-analytics.comandhttps://ssl.google-analytics.com.
Prevention: When forking, decide upfront whether you want analytics — many forks delete the block by default.
Error: page renders but the language list is empty
Cause: All of the user's non-fork repositories have a null
language field (typical for documentation-only or assets-only
repositories).
Solution: Nothing to do — the #mylanguages block is hidden via
$('#mylanguages').hide(); when there are no languages to show.
Prevention: This is by design.
Error: only forks listed, message "All of this user's repositories seem to be forks"
Cause: The user genuinely has only forked repositories. The renderer
filters out forks before sorting, so an empty sorted array reaches the
template.
Solution: None — the message is the intended display. The user must publish original repositories on GitHub to populate the projects block.
Prevention: N/A — this reflects the source data accurately.
Error: organisation count is wrong
Cause: The itemCount counter is shared between the projects block
and the organisations block. If the user already has five featured
projects, no organisations are rendered.
Solution: Hard-fork and split the counter into projectCount and
orgCount if you need both blocks fully populated. Edit the run()
function in js/githubresume.js.
Prevention: This is a known quirk of the original code path.
7. FAQ
Q: Do I need to install anything to use this?
No. Visit https://resume.github.com/?yourusername after starring the
resume/resume.github.com repo on GitHub. There is no app, no extension,
no account.
Q: Why do I have to star the repo?
Starring is the project's opt-in mechanism. Without a star, the front-end shows the opt out screen rather than rendering your data. It also acts as a lightweight "thanks" to the project maintainers.
Q: Can I look up someone else's résumé?
Yes — the URL ?username works for any public user or organisation,
provided that user has starred the repo. If they haven't, you'll see the
opt-out screen.
Q: Is any data sent to a server I don't control?
The page makes calls to api.github.com (public REST API, JSONP) and to
Google Analytics (google-analytics.com). No other endpoints are
contacted. There is no first-party backend in this project.
Q: How do I stop people from rendering my profile?
Unstar resume/resume.github.com on GitHub. The next visitor will see
the opt-out screen.
Q: Can I use my own domain?
Yes — fork the repo, edit css/resume.css to your taste, and serve the
files from any static host (GitHub Pages, S3 + CloudFront, Netlify,
Vercel). See Section 2, Path B.
Q: Does this work on mobile?
Yes for viewing. Printing from mobile browsers is generally less reliable than desktop — the Save as PDF destination is hidden behind different menus depending on the OS. For best results, generate PDFs from a desktop browser.
Q: Why is my year listed as "last year" or "this year"?
The renderer collapses year labels relative to the current year for recently-created GitHub accounts. If you joined this calendar year, you will see this year; if you joined the previous calendar year, last year. Older accounts show the four-digit year.
Q: Why does my contribution list look truncated?
GitHub's search-issues API caps results at 1000 items. Users with very high contribution counts will only see the first page of merged PRs. The project does not work around this limit.
Q: Will an organisation account work?
Yes. The renderer checks data.type === 'Organization' and uses
views/resumeOrgs.html instead of the user template. The avatar URL is
also rewritten to a Gravatar-only form for organisation accounts.
Q: How do I report a bug?
Open an issue at https://github.com/resume/resume.github.com/issues.
Include the username you were trying to render, the browser version, and
any console output. Note that the project has been quiet for years.
Q: Where is the source for the screen I'm seeing?
Each visible state corresponds to one HTML file in views/:
index.html (landing), resume.html (user résumé), resumeOrgs.html
(organisation résumé), job.html (one project row), org.html (one
organisation row), contrib.html (one contribution row), error.html,
api_limit.html, not_found.html, opt_out.html.
Q: Is there a CLI?
No. The project is browser-only.
Q: Is there a public API?
No. The project consumes GitHub's REST API but exposes none of its own.
Q: Are there any configuration files?
No. Apart from config.ru (which is two lines of Rack to serve static
files), there are no configuration files.