Thank you for your interest in contributing to JSON Resume! This document provides guidelines and instructions for contributing to the project.
- Code of Conduct
- Getting Started
- Development Setup
- Project Structure
- Code Standards
- Testing
- Pull Request Process
- Commit Message Guidelines
- Contributing Themes
This project focuses exclusively on technical excellence. We expect all contributors to:
- Be professional and respectful
- Focus on code quality and technical merit
- Provide constructive feedback in code reviews
- Follow the established coding standards
-
Fork the repository on GitHub
-
Clone your fork locally:
git clone https://github.com/YOUR_USERNAME/jsonresume.org.git cd jsonresume.org -
Add upstream remote:
git remote add upstream https://github.com/jsonresume/jsonresume.org.git
-
Create a feature branch:
git checkout -b feature/your-feature-name
- Node.js 18+ (LTS recommended)
- pnpm 8.15.9+
- Git
- Supabase CLI (for database operations)
-
Install dependencies:
pnpm install
-
Set up environment variables:
# Copy the example file cp apps/registry/.env.example apps/registry/.env # Edit .env and add your credentials # See .env.example for required variables
-
Generate Prisma client:
pnpm --filter registry db:generate
-
Start development server:
pnpm dev
This starts:
- Registry app: http://localhost:3000
- Homepage: http://localhost:3001 (if applicable)
The registry app uses Supabase for data storage.
Database name: registry
# Link to your Supabase project
supabase link --project-ref your-project-ref
# Pull latest schema
supabase db pull
# Run migrations (if any)
supabase db pushjsonresume.org/
├── apps/
│ ├── homepage/ # Marketing site
│ ├── homepage2/ # New marketing site
│ └── registry/ # Main resume registry app
├── packages/
│ ├── resume-core/ # Framework-agnostic theme primitives
│ ├── ats-validator/ # ATS compatibility validation
│ ├── themes/ # Resume theme implementations
│ │ ├── jsonresume-theme-reference/
│ │ ├── jsonresume-theme-modern/
│ │ ├── jsonresume-theme-standard/
│ │ └── ... (13+ themes)
│ ├── ui/ # Shared UI components
│ └── eslint-config/ # Shared ESLint config
└── scripts/ # Utility scripts
CRITICAL: All files must be ≤150 lines. No exceptions.
If a file exceeds 150 lines:
- Extract business logic into hooks (
useFeatureName.ts) - Split UI into sub-components
- Move utilities to helper files
- Follow the structure in
CLAUDE.md
feature/
├── index.ts # Public API exports
├── FeatureComponent.tsx # Main component (<150 lines)
├── useFeatureLogic.ts # Business logic hook
├── FeatureHelpers.ts # Pure utility functions
├── FeatureTypes.ts # TypeScript types
└── __tests__/ # Tests
├── FeatureComponent.test.tsx
└── useFeatureLogic.test.ts
- Use TypeScript for all new code
- Avoid
any- use proper types orunknown - Export types for public APIs
- Use strict mode
- Use Tailwind CSS for styling
- Follow existing component patterns
- Mobile-first responsive design
- Dark mode support where applicable
IMPORTANT: Always use Vercel AI SDK v5 (ai package):
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
const { text } = await generateText({
model: openai('gpt-4o-mini', {
apiKey: process.env.OPENAI_API_KEY,
}),
prompt: 'Your prompt here',
});❌ DO NOT use OpenAI SDK directly for chat completions ✅ DO use Vercel AI SDK for unified API across providers
# Run all tests
pnpm test
# Run tests for specific package
pnpm --filter registry test
# Run E2E tests
pnpm test:e2e- Unit tests: >80% coverage for utilities and logic
- Component tests: >70% coverage for UI components
- Integration tests: All API routes must have tests
- E2E tests: Critical user flows (login, create resume, export)
// Component test example
import { render, screen } from '@testing-library/react';
import { FeatureComponent } from './FeatureComponent';
describe('FeatureComponent', () => {
it('renders correctly', () => {
render(<FeatureComponent />);
expect(screen.getByText('Expected Text')).toBeInTheDocument();
});
});-
Update your branch:
git fetch upstream git rebase upstream/master
-
Run quality checks:
pnpm lint # ESLint pnpm prettier # Format check pnpm test # All tests pnpm build # Build check
-
Verify file sizes:
# Check if any files exceed 150 lines find . -name "*.ts" -o -name "*.tsx" -o -name "*.js" | xargs wc -l | awk '$1 > 150'
✅ Required:
- All tests passing
- No ESLint errors
- Code formatted with Prettier
- All files ≤150 lines
- Test coverage maintained/improved
- No new security vulnerabilities (
pnpm audit) - Documentation updated (if needed)
## Description
[Clear description of changes]
## Related Issues
Closes #123, Relates to #456
## Changes Made
- [ ] Feature/fix 1
- [ ] Feature/fix 2
## Testing
- [ ] Unit tests added/updated
- [ ] Integration tests added/updated
- [ ] E2E tests added/updated
- [ ] Manual testing completed
## Performance Impact
[Any performance implications]
## Breaking Changes
[Any breaking changes]
## Screenshots/Demos
[If UI changes]- Automated checks must pass (CI/CD)
- Code review by maintainer(s)
- Address feedback and update PR
- Squash and merge once approved
We use Conventional Commits:
<type>(<scope>): <subject>
[optional body]
[optional footer]
feat: New featurefix: Bug fixdocs: Documentation onlystyle: Code style (formatting, semicolons, etc.)refactor: Code refactoringperf: Performance improvementtest: Adding/updating testschore: Maintenance tasksci: CI/CD changes
feat(api): add cover letter generation endpoint
Implements AI-powered cover letter generation using Vercel AI SDK v5.
Uses GPT-4o-mini for content generation.
Closes #123
fix(auth): resolve GitHub OAuth redirect issue
The callback URL was incorrectly configured for production environment.
Updated to use dynamic URL based on deployment context.
Fixes #456
Want to add your resume theme to the registry? Here's everything you need to know.
The fastest way to build a new theme is using our composable component library:
// index.js
import {
Section,
SectionTitle,
ListItem,
DateRange,
BadgeList,
} from '@jsonresume/core';
export function render(resume) {
const html = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>${resume.basics.name}</title>
<link rel="stylesheet" href="https://unpkg.com/@jsonresume/core@0.3.1/src/styles/tokens.css">
<style>
body {
font-family: var(--resume-font-sans);
max-width: var(--resume-max-width);
margin: 0 auto;
padding: 40px 20px;
}
</style>
</head>
<body>
${Section({
children: `
<h1>${resume.basics.name}</h1>
<p>${resume.basics.label}</p>
`,
})}
${Section({
children: `
${SectionTitle({ text: 'Work Experience' })}
${resume.work
.map(
(job) => `
${ListItem({
title: job.position,
subtitle: job.company,
date: DateRange({
startDate: job.startDate,
endDate: job.endDate,
}),
description: job.summary,
})}
`
)
.join('')}
`,
})}
${Section({
children: `
${SectionTitle({ text: 'Skills' })}
${resume.skills
.map(
(skill) => `
<div>
<strong>${skill.name}</strong>
${BadgeList({ items: skill.keywords })}
</div>
`
)
.join('')}
`,
})}
</body>
</html>
`;
return html;
}Benefits:
- ✅ ATS-friendly by default - semantic HTML, standard fonts
- ✅ 10x faster development - no need to write HTML from scratch
- ✅ Tested components - all primitives have unit tests
- ✅ Design tokens - consistent styling with CSS variables
- ✅ Framework-agnostic - works with any setup
Available Components:
Section()- Wrapper for resume sectionsSectionTitle()- Styled section headingsListItem()- Experience/education entriesDateRange()- Start/end date displayBadge()/BadgeList()- Skills, keywords, tags
See Working Examples:
packages/themes/jsonresume-theme-reference/- Complete implementationpackages/themes/jsonresume-theme-modern/- Card-based modern designpackages/resume-core/README.md- Full API documentation
CRITICAL: Themes must be serverless-compatible. The registry runs on Vercel's serverless functions, which means:
❌ Cannot use:
fs.readFileSync()or any filesystem operationsfs.readFile(),fs.readdirSync(), etc.__dirnameor__filenamefor file paths- Dynamic file loading at runtime
✅ Must use:
- ES6 imports for templates and styles
- Build-time bundling (Vite, webpack, rollup)
- All assets inlined at compile time
- OR @jsonresume/core components (recommended)
// index.js
const fs = require('fs');
const Handlebars = require('handlebars');
function render(resume) {
const template = fs.readFileSync(__dirname + '/template.hbs', 'utf-8');
const css = fs.readFileSync(__dirname + '/style.css', 'utf-8');
return Handlebars.compile(template)({
css: `<style>${css}</style>`,
resume,
});
}
module.exports = { render };// index.js
import Handlebars from 'handlebars';
import template from './template.hbs?raw'; // Vite raw import
import css from './style.css?inline'; // Vite inline import
export function render(resume) {
return Handlebars.compile(template)({
css: `<style>${css}</style>`,
resume,
});
}-
Install Vite and plugins:
npm install --save-dev vite vite-plugin-handlebars
-
Create
vite.config.js:import { defineConfig } from 'vite'; export default defineConfig({ build: { lib: { entry: './index.js', formats: ['cjs'], fileName: 'index', }, rollupOptions: { external: ['handlebars'], }, }, });
-
Update imports to use Vite's special imports:
import template from './template.hbs?raw'; import css from './style.css?inline';
-
Add build script to
package.json:{ "scripts": { "build": "vite build", "prepublishOnly": "npm run build" }, "main": "./dist/index.cjs" } -
Build and test:
npm run build npm publish
For simple themes, you can inline content directly:
// index.js
import Handlebars from 'handlebars';
const template = `
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; }
/* your styles here */
</style>
</head>
<body>
<h1>{{resume.basics.name}}</h1>
<!-- your template here -->
</body>
</html>
`;
export function render(resume) {
return Handlebars.compile(template)({ resume });
}-
Install your theme in the registry:
cd jsonresume.org pnpm --filter registry add your-theme-name -
Add it to
themeConfig.js:// apps/registry/lib/formatters/template/themeConfig.js export const THEMES = { // ... existing themes 'your-theme': require('your-theme-name'), };
-
Start the dev server:
pnpm dev
-
Test your theme:
http://localhost:3000/thomasdavis?theme=your-theme
Check these themes in the repo for reference:
- @jsonresume/core components:
packages/themes/jsonresume-theme-reference(complete example) - @jsonresume/core with custom styles:
packages/themes/jsonresume-theme-modern(card-based design) - Simple approach:
packages/themes/jsonresume-theme-standard - Vite bundling:
packages/themes/jsonresume-theme-professional - Handlebars templates:
packages/themes/jsonresume-theme-spartacus
Cause: Vite isn't bundling your files
Fix: Ensure you're using ?raw or ?inline suffixes for non-JS imports
Cause: Import path is incorrect
Fix: Use relative paths (./template.hbs) not absolute paths
Cause: Still using fs somewhere
Fix: Search your code for require('fs') or fs.readFile
Once your theme is serverless-compatible:
- Publish to npm (if not already published)
- Open an issue at #36
- Provide:
- Theme name
- npm package name
- Link to repository
- Confirmation that it doesn't use
fsoperations
We'll review and add it to the registry!
- Examples: See
packages/directory in this repo - Questions: Comment on issue #36
- Stuck: We're happy to help! Just ask.
- Documentation: Check README.md and CLAUDE.md
- Issues: Search existing issues or create a new one
- Discussions: Ask questions in GitHub Discussions
- Security: See SECURITY.md for reporting vulnerabilities
Thank you for contributing to JSON Resume! 🎉