Local-first resume builder: a FastAPI backend renders JSON Resume data through themeable Jinja2 templates to HTML/PDF/PNG (headless Chromium), with a React editor UI, AI-assisted PDF import, and a server-side resume library.
# Backend deps (one-time)
./scripts/setup.sh # or: pip install -e . && playwright install chromium
# Frontend (one-time, then after UI changes)
cd web && npm install && npm run build && cd ..
# Run — serves the API and the built UI on one port
.venv/bin/python -m uvicorn src.main:app --port 8010
# open http://127.0.0.1:8010/For frontend development with hot reload, run npm run dev in web/
(it proxies /api to port 8010).
PDF/PNG rendering uses Playwright's bundled Chromium and falls back to a system-installed Google Chrome when the bundled browser is unavailable.
- Editor — schema-driven forms for all JSON Resume sections, plus a raw JSON escape hatch.
- Themes —
themes/<name>/each with atheme.tomlmanifest declaring design tokens (colors, fonts) and options (toggles/enums), all adjustable live in the UI:red-twocolumn— serif two-column with accent header bandslate-modern— dark full-bleed sidebar, contemporary sansats-classic— single-column, system fonts, ATS-safeexecutive-serif— centered classic serifteal-banner— full-width banner header over two columnsminimalist-mono— Swiss minimal with left-gutter section labelstimeline-pro— vertical timeline experiencecompact-tech— dense two-column for content-heavy resumes
- Font & color controls — per-theme font pickers (Google Fonts loaded on demand) and editable color tokens, with logical page breaks for clean PDFs.
- Live preview — debounced HTML/PDF preview; one-click export to PDF, PNG, or JSON.
- Persistence — the workspace autosaves to localStorage; named resumes
save to
resumes/<id>/resume.jsonvia the library controls (save / load / delete). - Variants — per-target overlays that reorder or hide sections
(stored under
x-meta). - AI import — upload an existing resume PDF; a local LLM extracts structured data for review before committing.
- Profile photo — upload, auto-downscale, and an optional AI background blur (MediaPipe selfie segmentation, with an offline soft-focus fallback).
| Method | Path | Purpose |
|---|---|---|
| GET | /api/themes |
Theme manifests (tokens + options) |
| GET | /api/resumes |
Library listing (id, name, label, updated_at) |
| POST | /api/resumes |
Create/replace a resume |
| GET/PUT/DELETE | /api/resumes/{id} |
Load / update / delete |
| POST | /api/render |
Render {resume, theme, variant, format, tokens, options} |
| POST | /api/import/upload |
AI extraction from an uploaded PDF |
.venv/bin/python -m pytest tests/ -q # test suite
.venv/bin/python -m ruff check src tests # lint
cd web && npm run build # typecheck + bundle the UICreate themes/<name>/ with a theme.toml (name, engine, entrypoint,
consumed sections, token defaults, options) and the Jinja2 template it points
at. Tokens become CSS custom properties; options are available as options.*
in the template. An optional style.css is rendered through Jinja and inlined
into every output format. The theme appears in the UI automatically.
AI import calls a local OpenAI-compatible LLM endpoint (e.g. llama.cpp, Ollama, vLLM). Defaults can be overridden with environment variables:
| Variable | Default | Purpose |
|---|---|---|
RESUMEFORGE_LLM_URL / OPENAI_BASE_URL |
http://127.0.0.1:8080/v1 |
OpenAI-compatible base URL |
RESUMEFORGE_LLM_MODEL / OPENAI_MODEL |
qwen3.6-35B |
Model name |
RESUMEFORGE_LLM_THINKING |
0 |
Set 1 to keep a reasoning model's chain-of-thought |
The AI photo background blur downloads its segmentation model from a CDN on first use (cached afterward); offline, it falls back to a soft-focus blur.
GPLv3 © OpenSensor.