ResumeCraft
Build professional Word (.docx) and PDF resumes from a simple JSON file.
ResumeCraft is a Python resume/CV generator that takes JSON input and produces a polished Word document or PDF. Use it as a library, a CLI tool, or drop it into a web app (FastAPI, Flask, Django).
See the generated PDF (built from sample_resume.json) to get a feel for the formatting. Set style.ats = true for ATS-friendly output.
Features
- Resume content lives in a single JSON file, easy to version control
- Auto-bolds keywords across all bullet points
- Right-aligned dates using proper tab stops
- Clickable hyperlinks for email, LinkedIn, GitHub, and projects
- Keeps section headings from getting orphaned at page breaks
- Pydantic-validated JSON input
- Custom section ordering and headings
- Sections: experience, projects (with multi-link support), skills, education, certifications, awards, languages
- Style presets: 7 fonts, 6 color themes, 3 spacing options
- Watch mode with PDF output for live preview
- Optional PDF export via
docx2pdf - JSON schema for editor autocomplete
Installation
pip install resumecraft # library + CLI
pip install resumecraft[pdf] # + PDF export
pip install resumecraft[pdf,watch] # + PDF export + watch mode
pip install resumecraft[all] # everything
# Install the CLI globally
pipx install resumecraft
uv tool install resumecraft
# Or run without installing
uvx resumecraft build my-resume.json
uvx "resumecraft[all]" build my-resume.json --open
Quick Start
# 1. Generate a template
resumecraft init -o my-resume.json
# 2. Edit my-resume.json with your details
# 3. Build your resume (docx)
resumecraft build my-resume.json
# 4. Build a PDF with the same filename (my-resume.pdf)
resumecraft build my-resume.json --pdf
# 5. Build and open immediately
resumecraft build my-resume.json --pdf --open
# 6. Validate without building
resumecraft validate my-resume.json
# 7. Watch for changes and rebuild automatically (defaults to PDF)
resumecraft watch my-resume.json --open
CLI Reference
resumecraft --help Show all commands
resumecraft --version Show version
resumecraft init -o FILE Generate a blank JSON template
resumecraft build FILE [-o FILE] [--pdf] [--open] Build .docx or .pdf from JSON
resumecraft validate FILE Validate JSON without building
resumecraft watch FILE [-o FILE] [--open] Watch and rebuild on file changes
When -o is omitted from build, the output is named after the input file with a timestamp, e.g., my-resume_2026-04-01_03-45pm.docx.
When -o is omitted from watch, the output defaults to <input-name>.pdf. PDF viewers don't lock files, so changes appear instantly.
Use as a Library
from resumecraft import ResumeCraft
# Load from anywhere
rc = ResumeCraft.from_jsonfile("my-resume.json")
rc = ResumeCraft.from_yamlfile("my-resume.yaml") # needs: pip install resumecraft[yaml]
rc = ResumeCraft.from_json('{"name": "John", ...}') # JSON string
rc = ResumeCraft.from_yaml("name: John\n...") # YAML string
rc = ResumeCraft.from_bytes(uploaded_bytes) # raw bytes (uploads, request body)
rc = ResumeCraft.from_dict({"name": "John", ...}) # dict
# Export
rc.to_docx("resume.docx") # save .docx
rc.to_pdf("resume.pdf") # save .pdf (needs: pip install resumecraft[pdf])
rc.to_docx_bytes() # docx as bytes (for streaming)
rc.to_pdf_bytes() # pdf as bytes
rc.to_dict() # back to a dict
# Discover the schema
ResumeCraft.sample() # sample dict with all fields
ResumeCraft.json_schema() # JSON schema (for editor autocomplete)
FastAPI example
import io
from fastapi import FastAPI, UploadFile
from fastapi.responses import Response, StreamingResponse
from resumecraft import ResumeCraft
app = FastAPI()
@app.post("/resume/docx")
def generate_docx(data: dict):
rc = ResumeCraft.from_dict(data)
return StreamingResponse(
io.BytesIO(rc.to_docx_bytes()),
media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
headers={"Content-Disposition": "attachment; filename=resume.docx"},
)
@app.post("/resume/pdf")
def generate_pdf(data: dict):
return Response(
ResumeCraft.from_dict(data).to_pdf_bytes(),
media_type="application/pdf",
)
@app.post("/resume/upload")
async def from_upload(file: UploadFile):
rc = ResumeCraft.from_bytes(await file.read())
return Response(rc.to_pdf_bytes(), media_type="application/pdf")
Flask example
import io
from flask import Flask, request, send_file
from resumecraft import ResumeCraft
app = Flask(__name__)
@app.post("/resume/docx")
def generate_docx():
rc = ResumeCraft.from_dict(request.json)
return send_file(
io.BytesIO(rc.to_docx_bytes()),
mimetype="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
download_name="resume.docx",
)
@app.post("/resume/pdf")
def generate_pdf():
pdf = ResumeCraft.from_dict(request.json).to_pdf_bytes()
return send_file(io.BytesIO(pdf), mimetype="application/pdf", download_name="resume.pdf")
See the examples/ folder for more complete examples including Django.
Advanced usage
The Pydantic model and the renderer are available for power users who need more control:
import json
from pathlib import Path
from resumecraft.models import Resume
from resumecraft.builder import DocxBuilder
data = json.loads(Path("my-resume.json").read_text())
resume = Resume.model_validate(data)
DocxBuilder(resume).save("resume.docx")
JSON Structure
Run resumecraft init to generate a full template. Here's the structure:
{
"$schema": "https://raw.githubusercontent.com/mdfarhankc/resumecraft/main/schema.json",
"name": "Your Name",
"contact": {
"location": "City, Country",
"email": "your@email.com",
"phone": "+1-234-567-8900",
"links": [
{ "label": "LinkedIn", "url": "https://linkedin.com/in/you" },
{ "label": "GitHub", "url": "https://github.com/you" }
]
},
"summary": "A brief professional summary...",
"bold_keywords": ["FastAPI", "React", "PostgreSQL"],
"experience": [
{
"company": "Company Name",
"location": "City, Country",
"title": "Your Title",
"duration": "JAN 2023 - PRESENT",
"bullets": ["What you did and the impact it had."]
}
],
"projects": [
{
"name": "Project Name",
"subtitle": "| Description",
"tech_stack": "Python, FastAPI",
"link": null,
"bullets": ["What you built."]
}
],
"professional_projects": [
{
"name": "Project Name",
"subtitle": "| Location | Type",
"tech_stack": "FastAPI, React, PostgreSQL",
"link": null,
"bullets": ["What you built."]
}
],
"personal_projects": [
{
"name": "Side Project",
"subtitle": "| Personal Project",
"tech_stack": null,
"links": [
{ "label": "GitHub", "url": "https://github.com/you/project" },
{ "label": "Live Demo", "url": "https://project.example.com" }
],
"bullets": ["What you built and why."]
}
],
"skills": [
{ "category": "Backend", "items": "Python (FastAPI, Django), Node.js" },
{ "category": "Frontend", "items": "React, TypeScript" }
],
"education": [
{
"institution": "University Name",
"degree": "Bachelor of Computer Science",
"duration": "2019 - 2023"
}
],
"certifications": [
{
"name": "AWS Certified Developer",
"issuer": "Amazon Web Services",
"date": "2024",
"link": { "label": "Verify", "url": "https://aws.amazon.com/verify" }
}
],
"awards": [
{
"title": "Employee of the Year",
"issuer": "Acme Corp",
"date": "2024",
"description": "Recognized for outstanding contributions."
}
],
"languages": "English - Native | Hindi - Professional",
"style": {
"font": "calibri",
"color": "black",
"spacing": "normal"
},
"section_order": [
"summary",
"experience",
"professional_projects",
"personal_projects",
"skills",
"education",
"certifications",
"awards",
"languages"
]
}
Field Reference
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Full name displayed at the top |
contact |
object | Yes | Location, email, phone, and links |
summary |
string | Yes | Professional summary paragraph |
bold_keywords |
string[] | No | Words to auto-bold in all bullet points |
experience |
object[] | No | Work experience entries |
projects |
object[] | No | Single unified projects section |
professional_projects |
object[] | No | Client/employer projects (use with personal_projects for split sections) |
personal_projects |
object[] | No | Side projects and open source work |
skills |
object[] | No | Categorized skill lists |
education |
object[] | No | Degrees and institutions |
certifications |
object[] | No | Professional certifications with issuer, date, and optional verification link |
awards |
object[] | No | Awards and achievements with title, issuer, date, and optional description |
languages |
string | No | Language proficiencies |
section_order |
string[] | No | Custom order of sections (omit for default). Only listed sections are rendered. |
headings |
object | No | Custom section headings (e.g., {"summary": "ABOUT ME"}) |
style |
object | No | Styling options (font, color, spacing) |
Style Options
Add a style object to customize the look of your resume:
{
"style": {
"font": "garamond",
"color": "navy",
"spacing": "compact"
}
}
| Option | Choices | Default |
|---|---|---|
font |
calibri, arial, times, garamond, georgia, helvetica, cambria |
calibri |
color |
black, navy, forest, maroon, slate, royal |
black |
spacing |
compact, normal, relaxed |
normal |
ats |
true, false |
false |
Set "ats": true to strip tab stops, colored tech lines, and heading borders for cleaner parsing by applicant tracking systems.
Available sections for section_order
summary, experience, projects, professional_projects, personal_projects, skills, education, certifications, awards, languages
Custom headings
Override any section's heading text:
{
"headings": {
"summary": "ABOUT ME",
"experience": "CAREER",
"awards": "HONORS"
}
}
Editor autocomplete (JSON schema)
Reference the schema in your JSON for autocomplete and validation in VS Code, IntelliJ, etc.:
{
"$schema": "https://raw.githubusercontent.com/mdfarhankc/resumecraft/main/schema.json",
"name": "Your Name",
...
}
Note: Use either
projectsfor a single section, orprofessional_projects+personal_projectsfor two separate sections. If you useprojects, include it insection_order- it's not part of the default order.
Project Structure
resumecraft/
├── pyproject.toml
├── README.md
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── schema.json # JSON schema (regenerate with: make schema)
├── examples/
│ ├── library.py # Library usage
│ ├── web.py # FastAPI/Flask/Django pattern
│ ├── sample_resume.json
│ └── output/sample_resume.pdf
├── src/resumecraft/
│ ├── __init__.py
│ ├── craft.py # ResumeCraft (entry point)
│ ├── cli.py # CLI commands
│ ├── builder.py # DocxBuilder (slim, drives the rendering)
│ ├── sections.py # Section classes + SECTION_REGISTRY
│ ├── render.py # RenderContext + paragraph helpers
│ ├── models.py # Pydantic data models
│ ├── styles.py # font/color/spacing maps
│ └── utils.py # low-level docx helpers (hyperlinks, borders)
└── tests/
├── fixtures/sample.json
├── test_models.py
├── test_builder.py
├── test_craft.py
├── test_cli.py
└── test_utils.py
Development
git clone https://github.com/mdfarhankc/resumecraft.git
cd resumecraft
uv sync --extra dev
Run tests
uv run pytest
Build
uv build
This creates dist/resumecraft-x.x.x.tar.gz and dist/resumecraft-x.x.x-py3-none-any.whl.
Publish to PyPI
Create a GitHub release (e.g., v0.2.0) and the CI workflow will publish to PyPI automatically via trusted publishing.
License
MIT
Metadata
Release files for resumecraft 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| resumecraft-0.6.0.tar.gz | 375.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| resumecraft-0.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 397.0 kB
Release files / resumecraft-0.6.0.tar.gz
| Download URL | resumecraft-0.6.0.tar.gz |
|---|---|
| Size | 375.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8ca2368eeb7bec1c92c80ee033d7862b4aa3815d8165999609b930b4a4e1b0e7
|
|
BLAKE2b-256 checksum How to use checksums |
b4bbc5d424b8c378ba84f30640e3f3b669e94823e3c477165127c4ef1fb59a8f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 25, 2026.
Transparency logRelease files / resumecraft-0.6.0-py3-none-any.whl
| Download URL | resumecraft-0.6.0-py3-none-any.whl |
|---|---|
| Size | 21.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
dc5719432d4511c250cb2a3299dc70309742c1a951137d6ada8e8273757b602e
|
|
BLAKE2b-256 checksum How to use checksums |
50033140be8c2d592b6d4ef38b488b41376d02ccb1593c0147ccbafc0892fdf5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 25, 2026.
Transparency log