Contributing to JSON Resume

July 16, 2026 ยท View on GitHub

Thank you for your interest in contributing to JSON Resume! This document provides guidelines and instructions for contributing to the project.

๐Ÿ“‹ Table of Contents

Code of Conduct

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

Getting Started

  1. Fork the repository on GitHub

  2. Clone your fork locally:

    git clone https://github.com/YOUR_USERNAME/jsonresume.org.git
    cd jsonresume.org
    
  3. Add upstream remote:

    git remote add upstream https://github.com/jsonresume/jsonresume.org.git
    
  4. Create a feature branch:

    git checkout -b feature/your-feature-name
    

Development Setup

Prerequisites

  • Node.js 18+ (LTS recommended)
  • pnpm 8.15.9+
  • Git
  • Supabase CLI (for database operations)

Installation

  1. Install dependencies:

    pnpm install
    
  2. 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
    
  3. Generate Prisma client:

    pnpm --filter registry db:generate
    
  4. Start development server:

    pnpm dev
    

    This starts:

Supabase Setup

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 push

Project Structure

jsonresume.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

Code Standards

File Size Limit

CRITICAL: All files must be โ‰ค150 lines. No exceptions.

If a file exceeds 150 lines:

  1. Extract business logic into hooks (useFeatureName.ts)
  2. Split UI into sub-components
  3. Move utilities to helper files
  4. Follow the structure in CLAUDE.md

Code Organization

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

TypeScript

  • Use TypeScript for all new code
  • Avoid any - use proper types or unknown
  • Export types for public APIs
  • Use strict mode

Styling

  • Use Tailwind CSS for styling
  • Follow existing component patterns
  • Mobile-first responsive design
  • Dark mode support where applicable

AI/LLM Integration

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

Testing

Running Tests

# Run all tests
pnpm test

# Run tests for specific package
pnpm --filter registry test

# Run E2E tests
pnpm test:e2e

Test Requirements

  • 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)

Writing Tests

// 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();
  });
});

Pull Request Process

Before Submitting

  1. Update your branch:

    git fetch upstream
    git rebase upstream/master
    
  2. Run quality checks:

    pnpm lint          # ESLint
    pnpm prettier      # Format check
    pnpm test          # All tests
    pnpm build         # Build check
    
  3. 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'
    

PR Requirements

โœ… 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)

PR Description Template

## 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]

Review Process

  1. Automated checks must pass (CI/CD)
  2. Code review by maintainer(s)
  3. Address feedback and update PR
  4. Squash and merge once approved

Commit Message Guidelines

We use Conventional Commits:

<type>(<scope>): <subject>

[optional body]

[optional footer]

Types

  • feat: New feature
  • fix: Bug fix
  • docs: Documentation only
  • style: Code style (formatting, semicolons, etc.)
  • refactor: Code refactoring
  • perf: Performance improvement
  • test: Adding/updating tests
  • chore: Maintenance tasks
  • ci: CI/CD changes

Examples

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

Contributing Themes

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 sections
  • SectionTitle() - Styled section headings
  • ListItem() - Experience/education entries
  • DateRange() - Start/end date display
  • Badge() / BadgeList() - Skills, keywords, tags

See Working Examples:

  • packages/themes/jsonresume-theme-reference/ - Complete implementation
  • packages/themes/jsonresume-theme-modern/ - Card-based modern design
  • packages/resume-core/README.md - Full API documentation

Theme Requirements

CRITICAL: Themes must be serverless-compatible. The registry runs on Vercel's serverless functions, which means:

โŒ Cannot use:

  • fs.readFileSync() or any filesystem operations
  • fs.readFile(), fs.readdirSync(), etc.
  • __dirname or __filename for 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)

Quick Start: Converting Your Theme

Before (โŒ Breaks on Vercel)

// 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 };

After (โœ… Works on Vercel)

// 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,
  });
}

Step-by-Step Migration Guide

  1. Install Vite and plugins:

    npm install --save-dev vite vite-plugin-handlebars
    
  2. Create vite.config.js:

    import { defineConfig } from 'vite';
    
    export default defineConfig({
      build: {
        lib: {
          entry: './index.js',
          formats: ['cjs'],
          fileName: 'index',
        },
        rollupOptions: {
          external: ['handlebars'],
        },
      },
    });
    
  3. Update imports to use Vite's special imports:

    import template from './template.hbs?raw';
    import css from './style.css?inline';
    
  4. Add build script to package.json:

    {
      "scripts": {
        "build": "vite build",
        "prepublishOnly": "npm run build"
      },
      "main": "./dist/index.cjs"
    }
    
  5. Build and test:

    npm run build
    npm publish
    

Option 2: Inline Everything Manually

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 });
}

Testing Your Theme Locally

  1. Install your theme in the registry:

    cd jsonresume.org
    pnpm --filter registry add your-theme-name
    
  2. Add it to themeConfig.js:

    // apps/registry/lib/formatters/template/themeConfig.js
    export const THEMES = {
      // ... existing themes
      'your-theme': require('your-theme-name'),
    };
    
  3. Start the dev server:

    pnpm dev
    
  4. Test your theme:

    http://localhost:3000/thomasdavis?theme=your-theme
    

Working Examples

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

Common Issues

Issue: "Cannot find module"

Cause: Vite isn't bundling your files Fix: Ensure you're using ?raw or ?inline suffixes for non-JS imports

Issue: "Template is undefined"

Cause: Import path is incorrect Fix: Use relative paths (./template.hbs) not absolute paths

Issue: Theme works locally but fails on Vercel

Cause: Still using fs somewhere Fix: Search your code for require('fs') or fs.readFile

Submitting Your Theme

Once your theme is serverless-compatible:

  1. Publish to npm (if not already published)
  2. Open an issue at #36
  3. Provide:
    • Theme name
    • npm package name
    • Link to repository
    • Confirmation that it doesn't use fs operations

We'll review and add it to the registry!

Need Help?

  • Examples: See packages/ directory in this repo
  • Questions: Comment on issue #36
  • Stuck: We're happy to help! Just ask.

Getting Help

Additional Resources


Thank you for contributing to JSON Resume! ๐ŸŽ‰