Generate professional resumes from JSON with DOCX/PDF export, custom styling, and web framework support.
Project description
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 samples to get a feel for the formatting:
- Styled PDF - navy theme, default layout
- ATS-friendly PDF - stripped formatting for applicant tracking systems
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
As a library
pip install resumecraft # Core library only
pip install resumecraft[pdf] # + PDF output support
As a CLI tool
pip install resumecraft[cli] # Core + CLI
pip install resumecraft[cli,pdf,watch] # Everything
pip install resumecraft[all] # Same as above
# Or install globally
pipx install "resumecraft[cli]"
uv tool install "resumecraft[cli]"
Run without installing
Use uvx to run the CLI in an ephemeral environment:
uvx "resumecraft[cli]" 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
resumecraft build my-resume.json
# 4. Build and open immediately
resumecraft build my-resume.json --open
# 5. Validate without building
resumecraft validate my-resume.json
# 6. 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] [--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 file is automatically named with a timestamp, e.g., 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
# From a JSON file
rc = ResumeCraft.from_json("my-resume.json")
rc.to_docx("resume.docx")
# From a dict
rc = ResumeCraft({"name": "John Doe", "contact": {...}, "summary": "..."})
rc.to_docx("resume.docx")
# From a JSON string
rc = ResumeCraft('{"name": "John Doe", ...}')
# Get bytes (for web frameworks)
content = rc.to_bytes()
# Export as PDF (requires: pip install resumecraft[pdf])
rc.to_pdf("resume.pdf")
# Export back to dict
data = rc.to_dict()
# Get a sample template to see all fields
sample = ResumeCraft.sample()
# Get JSON schema for editor validation
schema = ResumeCraft.json_schema()
FastAPI example
import io
import tempfile
from fastapi import FastAPI
from fastapi.responses import FileResponse, StreamingResponse
from resumecraft import ResumeCraft
app = FastAPI()
@app.post("/resume/docx")
def generate_docx(data: dict):
rc = ResumeCraft(data)
return StreamingResponse(
io.BytesIO(rc.to_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):
rc = ResumeCraft(data)
tmp = tempfile.NamedTemporaryFile(suffix=".pdf", delete=False)
rc.to_pdf(tmp.name)
return FileResponse(tmp.name, filename="resume.pdf", media_type="application/pdf")
Flask example
import io
import tempfile
from flask import Flask, request, send_file
from resumecraft import ResumeCraft
app = Flask(__name__)
@app.post("/resume/docx")
def generate_docx():
rc = ResumeCraft(request.json)
return send_file(
io.BytesIO(rc.to_bytes()),
mimetype="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
download_name="resume.docx",
)
@app.post("/resume/pdf")
def generate_pdf():
rc = ResumeCraft(request.json)
tmp = tempfile.NamedTemporaryFile(suffix=".pdf", delete=False)
rc.to_pdf(tmp.name)
return send_file(tmp.name, mimetype="application/pdf", download_name="resume.pdf")
See the examples/ folder for more complete examples including Django.
Advanced usage
You can also use the lower-level Resume and DocxBuilder classes directly:
from resumecraft import Resume, DocxBuilder
resume = Resume.from_json("my-resume.json")
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/resumecraft-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/resumecraft-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
├── LICENSE
├── examples/
│ ├── basic.py # Simple usage
│ ├── from_json.py # Build from JSON file
│ ├── styled.py # Custom fonts, colors, spacing
│ ├── fastapi_app.py # FastAPI REST API
│ ├── flask_app.py # Flask REST API
│ └── django_view.py # Django views
├── src/resumecraft/
│ ├── __init__.py # Public API (ResumeCraft, Resume, DocxBuilder)
│ ├── craft.py # ResumeCraft facade (simple high-level API)
│ ├── cli.py # Typer CLI commands (build, validate, init, watch)
│ ├── models.py # Pydantic data models
│ ├── builder.py # DocxBuilder - converts models to .docx
│ ├── styles.py # Styling constants (fonts, sizes, colors, margins)
│ └── utils.py # Helpers (hyperlinks, keep_with_next, bold patterns)
└── tests/
├── fixtures/
│ └── sample.json # Sample resume for tests
├── 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
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file resumecraft-0.5.0.tar.gz.
File metadata
- Download URL: resumecraft-0.5.0.tar.gz
- Upload date:
- Size: 568.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f6b6b00727b4b798274eb0e3d14bd4fad58e4c50dd8164be2195ac552219dc53
|
|
| MD5 |
7576b93de2971d10072dc18b4c8cd236
|
|
| BLAKE2b-256 |
ac4fba4b4ed676863aa0aa68555b34c3454ccd365282dbe219499a2057fc96f9
|
Provenance
The following attestation bundles were made for resumecraft-0.5.0.tar.gz:
Publisher:
publish.yml on mdfarhankc/resumecraft
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
resumecraft-0.5.0.tar.gz -
Subject digest:
f6b6b00727b4b798274eb0e3d14bd4fad58e4c50dd8164be2195ac552219dc53 - Sigstore transparency entry: 1348187862
- Sigstore integration time:
-
Permalink:
mdfarhankc/resumecraft@e6139b46bb2881c0986d706f4361c93c429e0fad -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/mdfarhankc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e6139b46bb2881c0986d706f4361c93c429e0fad -
Trigger Event:
release
-
Statement type:
File details
Details for the file resumecraft-0.5.0-py3-none-any.whl.
File metadata
- Download URL: resumecraft-0.5.0-py3-none-any.whl
- Upload date:
- Size: 19.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
247f253195d3406e5dac75b43ef2e5a9844a7fa94abbfc6c765204b45d56c093
|
|
| MD5 |
b51c28498e3817c3b7a2da0aa54d8c57
|
|
| BLAKE2b-256 |
3a3c893da83c39a302114351cd94ceeb1b08cbf6f803a23d712df0db35070036
|
Provenance
The following attestation bundles were made for resumecraft-0.5.0-py3-none-any.whl:
Publisher:
publish.yml on mdfarhankc/resumecraft
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
resumecraft-0.5.0-py3-none-any.whl -
Subject digest:
247f253195d3406e5dac75b43ef2e5a9844a7fa94abbfc6c765204b45d56c093 - Sigstore transparency entry: 1348187921
- Sigstore integration time:
-
Permalink:
mdfarhankc/resumecraft@e6139b46bb2881c0986d706f4361c93c429e0fad -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/mdfarhankc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e6139b46bb2881c0986d706f4361c93c429e0fad -
Trigger Event:
release
-
Statement type: