Automatically award badges to contributors when PRs merge — turn contributions into verifiable credentials. Built for open-source communities that want to recognize their contributors meaningfully.
→ https://certifier-demondie.vercel.app/
Sign in with GitHub to see your dashboard, track points, and claim badges.
- How It Works
- Tech Stack
- Setup
- Points & Tiers
- Roles & Permissions
- Architecture
- API Overview
- Documentation
- License
- A contributor merges a PR in a tracked GitHub repository.
- A webhook fires — the app awards points based on the PR's label
(e.g.,
frontend:imp→ +1 Frontend point). - Points accumulate — reach a threshold and a badge becomes available.
- The contributor claims the badge from their dashboard — issued as a verifiable digital credential via certifier.io.
- The credential is shareable — on LinkedIn, GitHub, or anywhere credentials are accepted.
| Layer | Technology |
|---|---|
| Framework | Next.js 14 (App Router) + TypeScript |
| Auth | NextAuth.js (v4) with GitHub OAuth |
| Database | Supabase (PostgreSQL + RLS + Realtime) |
| UI | Tailwind CSS + shadcn/ui + @base-ui/react |
| Badging | certifier.io REST API (digital credentials) |
| CI/CD | GitHub Actions + Vercel |
- Node.js 20+
- A Supabase project
- A GitHub OAuth App
- (Optional) A certifier.io account for digital credential issuance
git clone https://github.com/DemonDie/certifier
cd certifier
npm ciCopy .env.example to .env.local:
cp .env.example .env.localRequired variables:
| Variable | Description |
|---|---|
AUTH_SECRET |
NextAuth encryption key (openssl rand -base64 32) |
AUTH_GITHUB_ID |
GitHub OAuth App client ID |
AUTH_GITHUB_SECRET |
GitHub OAuth App client secret |
NEXT_PUBLIC_SUPABASE_URL |
Supabase project URL |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
Supabase anonymous key |
SUPABASE_SERVICE_ROLE_KEY |
Supabase service role key (admin operations) |
Optional variables:
| Variable | Description |
|---|---|
CERTIFIER_API_KEY |
Certifier API access token |
CERTIFIER_TEMPLATES |
JSON mapping of family:tier → group IDs |
Create a GitHub OAuth App at Settings → Developer Settings → OAuth Apps:
- Homepage URL:
http://localhost:3000 - Authorization callback URL:
http://localhost:3000/api/auth/callback - For production, replace
localhostwith your deployed URL
-
Create a Supabase project at supabase.com
-
Run migrations in order:
supabase migration up
Or apply manually via SQL editor:
supabase/migrations/001_initial_schema.sqlsupabase/migrations/002_rls_policies.sqlsupabase/migrations/003_realtime.sqlsupabase/migrations/004_helper_functions.sqlsupabase/migrations/005_seed_special_badges.sql
-
Enable Realtime on
profilesandbadge_claimstables
npm run devVisit http://localhost:3000 and sign in with GitHub.
-
Sign in with your GitHub account
-
Set your role to
adminin Supabase:UPDATE profiles SET role = 'admin' WHERE github_username = 'your-username';
-
Visit
/adminto configure:- GitHub org / username
- Tracked repos (
owner/repoformat) - Webhook secret
- Go to your GitHub repo → Settings → Webhooks → Add webhook
- Payload URL:
https://your-app.vercel.app/api/webhooks/github - Content type:
application/json - Secret: The webhook secret you configured in admin settings
- Events: Pull requests (select)
- Add labels like
frontend:impto merged PRs to award points
-
Create an account at certifier.io
-
Create badge templates (5 families × 4 tiers = 20 templates)
-
Create an API access token: Settings → Developers → Access Tokens
-
Map template group IDs in
CERTIFIER_TEMPLATES:{ "frontend:imp": "grp_xxx", "frontend:fiend": "grp_yyy", ... }
Badges are earned by accumulating points within each family. Points come from merged PRs — the label determines the family and tier.
| Tier | Per PR | Badge threshold |
|---|---|---|
| Imp | 1 point | 5 |
| Fiend | 3 points | 15 |
| Overlord | 9 points | 45 |
| Demon King | 27 points | 135 |
| Family | Icon | Focus Area |
|---|---|---|
| Frontend | 🎨 | UI code, components, accessibility |
| Backend | ⚙️ | Server logic, APIs, databases |
| Documentation | 📚 | Docs, tutorials, translations |
| Ideas | 💡 | Feature proposals, UX research |
| Community | 🤝 | Support, reviews, CI/CD |
| Role | Permissions |
|---|---|
| Contributor | View dashboard, claim badges, view contributions |
| Maintainer | All contributor + nominate/vote for special badges, view admin settings |
| Admin | All maintainer + edit admin settings, manage users, full access |
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ GitHub │────▶│ Webhook │────▶│ Supabase │
│ (PR merge) │ │ /api/... │ │ (database) │
└──────────────┘ └──────────────┘ └──────┬───────┘
│
┌──────────────────────────────┘
▼
┌──────────────────┐ ┌──────────────┐
│ Next.js App │◀───▶│ certifier.io│
│ (dashboard) │ │ (badges) │
└──────────────────┘ └──────────────┘
- Auth is handled by NextAuth.js, not Supabase Auth — the session's
user.idis mapped to theprofilestable (text ID, not UUID). All browser-side data queries use API routes backed by the Supabase admin client (service role key) to bypass RLS, sinceauth.uid()returns null for non-Supabase sessions. - RLS policies exist for direct database access but cast
auth.uid()totextfor compatibility with the profiles table's text ID column. - Badge claims use a unique constraint on
(user_id, family, tier)— each contributor can claim each badge tier only once.
| Route | Method | Purpose |
|---|---|---|
/api/user/profile |
GET | Current user's profile and points |
/api/badges/available |
GET | Badges available to claim |
/api/badges/claimed |
GET | Badges already claimed |
/api/badges/claim |
POST | Claim an available badge |
/api/webhooks/github |
POST | GitHub PR merge webhook |
/api/admin/settings |
GET/PUT | Admin configuration |
/api/special-badges/nominate |
POST | Nominate for a special badge |
/api/special-badges/vote |
POST | Vote on a special nomination |
| Service | Integration |
|---|---|
| GitHub | OAuth login + webhook (PR merge events) |
| Supabase | Database, RLS, Realtime subscriptions |
| certifier.io | Digital credential creation and issuance |
| Document | Description |
|---|---|
| CONTRIBUTING.md | How to contribute, PR workflow, label convention |
| SECURITY.md | Security policy and vulnerability reporting |
| CODE_OF_CONDUCT.md | Community standards and enforcement |
| Guides: Claiming Badges | Step-by-step for contributors to claim their earned badges |
| Guides: Maintainer Labels | Label format, setup, and point award system for maintainers |
| LICENSE | MIT license |
MIT © DemonDie