A fast, self-hosted, open-source alternative to Adobe Acrobat / iLovePDF. Merge, split, convert, watermark, compress and visually edit PDFs from a clean, modern web UI - no subscriptions, no watermarked exports, no uploads to a third party.
| Layer | Tech |
|---|---|
| Frontend | React + TypeScript + Vite + Tailwind CSS v4, pdf.js (rendering), fabric.js (visual editor), dnd-kit (drag-to-reorder) |
| API | Node.js + Express + TypeScript, pdf-lib for in-process PDF manipulation (used for local dev / Docker) |
| Converter | Python + FastAPI, PyMuPDF (page ops, rasterization, compression), pdf2docx (PDF→Word), LibreOffice headless (Word→PDF) |
Every PDF operation is implemented twice: once in Node (apps/api, via
pdf-lib) for local development and Docker deployments, and once in Python
(services/converter, via PyMuPDF) so the app can also run as a two-service
(web + converter) deployment on platforms like Vercel that don't run a
long-lived Node API. See CONTRIBUTING.md for what that
means when you change a PDF endpoint.
Everything runs locally / self-hosted. Uploaded files are held in memory only for the duration of a request and are never written to disk or persisted.
- Merge & Organize - combine multiple PDFs, drag pages into any order, rotate or delete pages, export as one file.
- Split - extract page ranges into separate PDFs (returned as a ZIP).
- Edit PDF - add and freely move text, images and shapes on any page (fabric.js canvas over a
pdf.js-rendered background), then flatten to a new PDF. - PDF ⇄ Word -
pdf2docxfor PDF→Word; LibreOffice headless for Word→PDF. - PDF ⇄ Images - rasterize pages to PNG at a chosen DPI, or build a PDF from a set of images.
- Watermark - centered or tiled text watermark with color/opacity/rotation controls.
- Page numbers - configurable position, start number and format string.
- Compress - re-encodes oversized embedded images and rebuilds the PDF object structure.
Prerequisites: Node.js 20+, Python 3.11+, and (optionally) LibreOffice for Word→PDF.
# 1. Install JS dependencies (root + both apps, via npm workspaces)
npm install --workspaces
# 2. Set up the Python converter service
npm run converter:install
# 3. Copy env defaults
Copy-Item apps/api/.env.example apps/api/.env
# 4. Run everything (API + web) in one terminal, converter in another
npm run converter:dev
npm run dev- Web: http://localhost:5173
- API: http://localhost:4000
- Converter: http://localhost:8001
docker compose up --buildServes the web app on http://localhost:8080, proxying /api to the Node
service, which in turn talks to the Python converter service. The converter
image bundles LibreOffice so Word→PDF works out of the box in Docker.
This repo includes a root vercel.json that defines two
services and routes all /api/* traffic straight to the Python converter
service (there is no Node API in this deployment path):
{
"services": {
"web": {
"root": "apps/web",
"framework": "vite",
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
},
"converter": { "root": "services/converter", "entrypoint": "main:app" }
},
"rewrites": [
{ "source": "/api(/.*)?", "destination": { "type": "service", "service": "converter" } },
{ "source": "/(.*)", "destination": { "type": "service", "service": "web" } }
]
}- This uses Vercel's Services feature
(not classic zero-config Functions), so the Python service needs an explicit
entrypoint: "main:app"pointing at the FastAPIappobject inservices/converter/main.py- there's noapi/folder involved. - The
webservice's ownrewritesfall back every path to/index.html. Without this, visiting a client-side route directly (e.g./tools/organize, or any unknown path meant to hit the custom 404 page) returns Vercel's generic static 404 instead of the React app, since the static file for that path doesn't actually exist on disk. - Import the repo into Vercel as a single project; it will pick up both
services from
vercel.jsonautomatically. - Known limitation: Word→PDF needs LibreOffice, which isn't available in Vercel's serverless Python runtime - that endpoint will return a clear 503 there. It works fully in Docker/self-hosted deployments (see below).
- Known risk:
pdf2docxpulls inopencv-python-headless+numpy, which can push the function bundle close to Vercel's size limits. If a deploy fails on size, that's the first place to look.
apps/
web/ React frontend (Vite)
api/ Express API - pdf-lib based; used for local dev & Docker
services/
converter/ FastAPI microservice - full PDF API (PyMuPDF) + pdf2docx / LibreOffice conversions
main.py entrypoint referenced by vercel.json as "main:app"
- Word→PDF requires LibreOffice (
soffice) installed on the machine running the converter service (bundled automatically in the Docker image; not available on Vercel). - The visual editor adds/moves new content and can redact/whiteout existing content with a filled rectangle; PDFs don't have reflowable text, so true in-place editing of pre-existing text works the same way professional PDF editors do it - cover + re-draw.
Contributions are welcome! Please read CONTRIBUTING.md for local setup, coding conventions, and what to check before opening a PR. This project follows the Code of Conduct.
Found a security issue? Please see SECURITY.md instead of opening a public issue.
MIT - do whatever you like with it, attribution appreciated.