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) |
Many classic PDF operations are implemented twice: once in Node (apps/api, via
pdf-lib) for local development and Docker deployments, and once in Python
(services/converter, via PyMuPDF). Native editing of original PDF elements is implemented in Python only; the Node API forwards those requests to the converter. This also allows 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.
S-PDF can be self-hosted. Many PDF operations use in-memory buffers, while some conversions write temporary files that are cleaned up after the operation. Review your deployment, logs, temporary storage and resource limits before processing sensitive documents.
- Architecture and deployment modes
- PDF editor capabilities, limitations and roadmap
- Quality checks and release criteria
- Native editing guide and limitations
- Licensing and third-party obligations
- Automated and manual testing
Editor status: Native editing is experimental: text spans, embedded raster images and simple line/rectangle paths can be moved, changed or removed. The editor displays the PDF as a dedicated read-only page layer with transparent controls above it, and groups editing tools into a compact toolbar. Complex vector graphics, OCR text and lossless layout editing are not supported. See the native editing guide and test matrix.
- 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 (experimental) - select and change existing PDF text spans and embedded images, move and resize selections, and add text, images, shapes or freehand highlights. Native changes use the Python converter and may not preserve complex formatting. Scanned text requires OCR, which is not yet included.
- 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 22.13+ (or Node.js 24+), Python 3.11+, and LibreOffice for Word→PDF. Python commands below expect python to be available.
Install dependencies in the repository root. Use a Python virtual environment so the converter packages do not modify your global Python installation.
On macOS or Linux:
npm install --workspaces
python -m venv .venv
source .venv/bin/activate
npm run converter:install
cp apps/api/.env.example apps/api/.envOn Windows PowerShell:
npm install --workspaces
python -m venv .venv
.venv\Scripts\Activate.ps1
npm run converter:install
Copy-Item apps/api/.env.example apps/api/.envIn the first terminal, activate the virtual environment and start npm run converter:dev. In another terminal, start npm run dev for the frontend and Node API. The Python service must be available for selecting or changing existing PDF content.
- 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 can reconstruct selected native text, raster images, and simple line/rectangle paths with PyMuPDF. Changes remove the selected original content where supported. Layout may change. A white overlay rectangle is not a secure redaction, and S-PDF has no security-verified privacy-redaction workflow.
- Original document fonts, complex graphics, scanned text, form fields, and annotation round-trips are not preserved by arbitrary changes. See Native Editing.
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.
S-PDF's own source is under MIT. The complete installed application includes separate dependency licenses. PyMuPDF/MuPDF is AGPLv3 or commercially licensed, and deploying or redistributing an integrated solution requires an appropriate licensing review. See Licensing and third-party obligations.