Free dynamic QR codes on GitHub Pages — host your own redirects, update destinations from a simple admin UI, no SaaS or reprinting.
QR scan → https://you.github.io/REPO/a7k2m9 → reads links.json → redirects
Each QR gets a permanent random ID (a7k2m9). Change where it points anytime — the printed code stays the same.
- Push this repo to GitHub
- Settings → Secrets and variables → Actions → New secret:
- Name:
ADMIN_PASSWORD - Value: your admin login password
- Name:
- Settings → Pages → Source: GitHub Actions
- Push to
main(or re-run the Deploy Pages workflow)
Site: https://YOUR_USERNAME.github.io/REPO/
Important: Pages must use GitHub Actions, not "Deploy from a branch". The workflow reads
ADMIN_PASSWORDand locks the admin page. Change the password anytime in Secrets, then redeploy.
- Open
https://YOUR_USERNAME.github.io/REPO/admin.html - Sign in with your
ADMIN_PASSWORD - Paste a GitHub token (repo scope) — stays in your tab only
- + New QR → set description + destination URL → copy the QR URL
- Save to GitHub
Or Download links.json and push manually if you prefer.
Changes go live in ~1 min.
| Where | What |
|---|---|
| Live site | GitHub Secret ADMIN_PASSWORD — set in repo Settings |
| Local dev | Copy .env.example → .env, run node scripts/setup-admin.mjs |
The password is never stored in the repo. GitHub Actions hashes it at deploy time.
Settings → Pages → Custom domain — then point your DNS at GitHub.
404.htmlis required so slug URLs like/a7k2m9work on GitHub Pageslinks.jsonis public — no secrets in it
| File | Purpose |
|---|---|
index.html |
Redirect script for the site root |
404.html |
Same redirect script — GitHub serves this for slug URLs like /a7k2m9 |
links.json |
Your QR map: ID → destination URL + description |
admin.html |
Password-protected UI to create and edit QRs |
admin-secrets.js |
Password hash for admin — generated at deploy, not in git |
.github/workflows/pages.yml |
Deploy workflow — builds the site and injects admin-secrets.js from your GitHub Secret |
scripts/setup-admin.mjs |
Local dev only — generates admin-secrets.js from .env |
Don't open admin.html directly — it won't work. Do this:
1. Set your local admin password
cp .env.example .envEdit .env and set ADMIN_PASSWORD=whatever-you-want
2. Generate admin-secrets.js
node scripts/setup-admin.mjsRun this again any time you change the password in .env.
3. Start a local server
python3 -m http.server 80804. Open admin
http://localhost:8080/admin.html
Sign in with the password from your .env file.
To stop the server: Ctrl+C in the terminal.