CLAHub provides a low-friction way to have a Contributor License Agreement for your open source project on GitHub. Contributors digitally sign your CLA by signing in with GitHub, and pull requests are automatically marked with a status check based on whether all commit authors have signed.
Running at cla-hub.io
For project owners:
- Create CLAs from templates (Apache ICLA, DCO) or write custom ones in Markdown
- Version control for CLA text with changelogs
- Custom form fields (text, email, URL, checkbox, date)
- Automatic GitHub Check Runs on pull requests
- Org-wide agreements — create a single CLA that covers all repos in a GitHub organization
- Role-based access — org admins can view agreements and signatories without full owner permissions
- Exclusion management — exclude individual users, GitHub teams, or bots from CLA requirements
- Manual signature entry — add signatures by GitHub username or email
- CSV import — bulk-import signatures with preview, validation, and duplicate detection
- Signature revocation — revoke and restore signatures with automatic PR status re-check
- Ownership transfer — transfer agreement ownership to another registered owner
- Email notifications — opt-in email when contributors sign, powered by Resend
- REST API — full CRUD for agreements and signatures with API key authentication (
clahub_-prefixed Bearer tokens) - SVG badges — embeddable shields.io-compatible status badges for READMEs (
/api/badge/{owner}/{repo}) - Rate limiting — tiered rate limits (100/60/30 req/min) with
X-RateLimit-*headers - CSV/PDF export — download signatures as CSV or agreement documents as PDF
- Corporate CLA (CCLA) — a company representative signs once, covering all contributors with a matching email domain
- Docker self-hosting —
docker compose up -dwith auto-migration, health checks, and SQLite volume persistence - Custom branding — white-label with
APP_NAME,APP_LOGO_URL,APP_PRIMARY_COLOR - CONTRIBUTING.md generation — auto-generate a CONTRIBUTING snippet with CLA signing link and badge
- Admin dashboard — audit log viewer and manual PR re-check on the agreement edit page
- Health endpoint —
GET /api/healthfor uptime monitoring and container health checks - WCAG 2.1 AA accessible — color contrast, ARIA labels, keyboard navigation, skip links
- Dashboard with signature tracking
- Full audit log of all changes
For contributors:
- Sign in with GitHub (minimal permissions)
- View rendered CLA, fill required fields, sign digitally
- Automatic PR re-check after signing — no need to re-push
CLAHub provides a REST API at /api/v1/ for programmatic access. Authenticate with a Bearer token (generate one under Settings > API Keys).
# List signatures for a repo agreement
curl -H "Authorization: Bearer clahub_xxxx" \
https://cla-hub.io/api/v1/agreements/my-org/my-repo/signatures
# Check if a user has signed
curl https://cla-hub.io/api/v1/agreements/my-org/my-repo/check/username
# Export signatures as CSV
curl -H "Authorization: Bearer clahub_xxxx" \
https://cla-hub.io/api/v1/agreements/my-org/my-repo/export/csvFull API documentation is available in the OpenAPI spec.
Add a CLA status badge to your README:
[](https://cla-hub.io/agreements/my-org/my-repo)Options: ?style=flat-square, ?label=License, ?color=4c1.
- Owner installs the CLAHub GitHub App on a repository (or organization)
- Owner creates a CLA agreement for that repo or org
- When a pull request is opened, CLAHub receives a webhook
- CLAHub extracts commit authors and checks each one:
- Excluded (bot pattern or manual exclusion) — skipped
- Signed — has a valid, non-revoked individual signature on file
- Corporate-covered — email domain matches an active corporate CLA signature
- Unsigned — needs to sign the CLA
- A GitHub Check Run is posted with the result and a link to the signing page
- After a contributor signs, all open PRs for that repo are re-checked
- Next.js 16 (App Router, React 19)
- Prisma v7 with SQLite (better-sqlite3 adapter)
- Auth.js (NextAuth v5) with dual GitHub OAuth providers
- Tailwind CSS v4 + shadcn/ui components
- Octokit for GitHub App / Checks API
- Zod v4 + React Hook Form for validation
- Resend for transactional email (optional)
- Sentry via
@sentry/nextjsfor error tracking (optional) - @react-pdf/renderer for PDF export
- PapaParse for CSV export
- Docker for self-hosted deployment
- Vitest + Playwright for testing
Full walkthrough: docs/getting-started.md
You need Node.js 20+ and npm. You also need a GitHub App and two OAuth Apps (owner + contributor). See the Getting Started guide for step-by-step setup.
git clone https://github.com/DamageLabs/clahub.git
cd clahub
npm ci
cp .env.local.example .env.local # fill in your values
npm run db:push
npm run devOpen http://localhost:3000.
Required env vars: DATABASE_URL, NEXTAUTH_SECRET, GITHUB_APP_ID, GITHUB_APP_PRIVATE_KEY, GITHUB_WEBHOOK_SECRET, GITHUB_OWNER_CLIENT_ID/SECRET, GITHUB_CONTRIBUTOR_CLIENT_ID/SECRET, APP_URL.
Optional: SENTRY_DSN, RESEND_API_KEY, LOG_LEVEL, branding vars (APP_NAME, APP_LOGO_URL, APP_PRIMARY_COLOR).
See docs/configuration.md for the full environment variable reference.
GitHub can't reach localhost, so use ngrok (ngrok http 3000) and update APP_URL in .env.local.
| Command | Description |
|---|---|
npm run dev |
Start dev server |
npm run build |
Production build |
npm run start |
Start production server |
npm run lint |
Run ESLint |
npm run format |
Format with Prettier |
npm test |
Run unit tests (Vitest) |
npm run test:watch |
Run tests in watch mode |
npm run test:e2e |
Run E2E tests (Playwright) |
npm run db:push |
Push Prisma schema to database |
npm run db:seed |
Seed database with sample data |
npm run db:studio |
Open Prisma Studio |
The fastest way to self-host CLAHub is with Docker:
cp .env.docker.example .env # fill in your values
docker compose up -dAlso supports Vercel, Railway, Fly.io, and bare-metal Node.js (PM2 / systemd).
See Deployment Guide for all options and Upgrading Guide for version updates.
Unit tests live in tests/unit/ and cover pure functions, Zod schema validation, and business logic with mocked dependencies.
npm test # run all unit tests once
npm run test:watch # run in watch mode during developmentTests are organized by layer:
| Directory | What's tested |
|---|---|
tests/unit/schemas/ |
Zod schemas — agreement, signing, exclusion |
tests/unit/lib/ |
Utility modules — api-error, audit, logger, result, sentry, badge, rate-limit, export-csv, export-pdf |
tests/unit/cla-check.test.ts |
extractPushAuthors() pure function |
tests/unit/cla-check-integration.test.ts |
checkClaForCommitAuthors, createCheckRun, extractPRAuthors with mocked Prisma/Octokit |
tests/unit/lib/cla-check-corporate.test.ts |
Corporate CLA domain-based coverage with mocked Prisma |
tests/unit/lib/api-sign-corporate.test.ts |
Corporate signing schema validation |
tests/unit/actions/signing.test.ts |
signAgreement server action with mocked auth, Prisma, and cla-check |
E2E tests live in tests/e2e/ and test the signing flow, dashboard, and webhook endpoint through a real browser.
npm run test:e2e # run all E2E testsLocal development: Playwright reuses your running dev server on port 3000. Make sure npm run dev is running and the database is seeded (npm run db:seed).
CI: Playwright starts its own dev server using .env.test and runs prisma db push --force-reset + prisma db seed against a disposable test.db via the global setup script.
Authentication in tests: E2E tests inject real JWT session cookies using @auth/core/jwt encode() with the same secret as the running server. See tests/e2e/auth-helpers.ts for the authenticateAs() helper.
First time setup:
npx playwright install chromium # download browser binary (one-time)src/
app/
(marketing)/ Landing, privacy, terms, why-cla pages
agreements/ Dashboard, create, edit, public signing page
api/auth/ NextAuth endpoints
api/badge/ SVG badge endpoints (public, no auth)
api/health/ Health check endpoint
api/v1/ REST API (agreements, signatures, check, export)
api/webhooks/ GitHub App webhook handler
auth/signin/ Sign-in page
error.tsx Error boundary with Sentry reporting
global-error.tsx Global error boundary (outside root layout)
not-found.tsx Custom 404 page
components/
ui/ shadcn/ui primitives
agreements/ Agreement form, signing form, exclusion manager, signature manager, notification toggle, audit log viewer, recheck button, CONTRIBUTING.md section
lib/
actions/ Server actions (agreement, exclusion, signing, signature)
schemas/ Zod validation schemas
api-auth.ts API key + session authentication
api-error.ts Structured API error responses with error codes
api-rate-limit.ts Tiered rate limiting (API key / session / anon)
audit.ts Shared audit logging utility
auth.ts NextAuth configuration (dual providers)
access.ts Role-based access control (owner, org_admin)
badge.ts SVG badge rendering (shields.io-compatible)
branding.ts Custom instance branding from env vars
contributing.ts CONTRIBUTING.md snippet generation
cla-check.ts Core CLA verification + exclusion + corporate coverage
email.ts Resend email wrapper + notification templates
export-csv.ts CSV export with papaparse
export-pdf.ts PDF export with @react-pdf/renderer
github.ts GitHub App / Octokit setup + webhook handlers
logger.ts Structured JSON logger with level filtering
prisma.ts Prisma client singleton
rate-limit.ts In-memory sliding window rate limiter
sentry.ts Sensitive data scrubbing utility
templates.ts CLA templates (Apache ICLA, DCO)
instrumentation.ts Server-side Sentry init (conditional on SENTRY_DSN)
instrumentation-client.ts Client-side Sentry init
prisma/
schema.prisma Database schema (9 models)
seed.ts Sample data
tests/
unit/ Vitest unit tests (schemas, lib, cla-check, actions)
e2e/ Playwright E2E tests (signing flow, dashboard, webhook)
setup.ts Vitest setup (jest-dom matchers)
Dockerfile Multi-stage production image (node:20-alpine)
docker-compose.yml Self-hosted deployment with SQLite volume
docker-entrypoint.sh Auto-migration entrypoint script
docs/ Deployment and operations documentation
| Model | Purpose |
|---|---|
User |
GitHub users (owners and contributors) |
Agreement |
CLA definitions linked to a GitHub repo or org |
AgreementVersion |
Versioned CLA text with changelogs |
AgreementField |
Custom form fields on a CLA |
Signature |
Individual or corporate signatures (with optional company name/domain/title) |
FieldEntry |
Field values submitted with a signature |
Exclusion |
Bot/user/team exclusions per agreement |
ApiKey |
API keys for REST API authentication |
AuditLog |
Complete change history |
Contributor License Agreements prove intellectual property provenance of contributions to an open-source project. They generally say:
- The code I'm contributing is mine, and I have the right to license it.
- I'm granting you a license to distribute said code under the terms of this agreement.
From Contributor License Agreements by Jacob Kaplan-Moss
More background:
- Wikipedia: Contributor License Agreement
- Harmony Agreements — a tool to help choose a CLA
None of the CLAHub documentation, functionality, or other communication constitutes legal advice. Consult your lawyer about contributor agreements for your project.
MIT — Copyright (c) 2013-2026 Jason Morrison, Tony Guntharp