Skip to content

Repository files navigation

Paperforge

An open source, self-hosted alternative to ILovePDF. Structural operations run in the browser, while OCR, conversions, and native compression are processed by isolated workers.

Paperforge is permanently free and accountless. It has no subscriptions, authentication, authorization, cloud dependencies, advertisements, usage tracking, or outbound telemetry. Operational limits only protect the local instance from exhausting CPU, memory, disk, or queue capacity.

Components

  • apps/web: Next.js, pdf.js, and pdf-lib in a Web Worker.
  • services/cmd/api: Go HTTP API and Asynq producer.
  • services/cmd/worker: Asynq consumer and native executors.
  • redis: task queue and ephemeral job state.
  • gotenberg: Office/HTML to PDF conversion.

The detailed architecture is available in docs/architecture.md.

Development

Requirements: Node.js 22+, Go 1.24+, Docker, and Docker Compose.

corepack enable
pnpm install
pnpm dev:web
pnpm test:web

To start the complete stack:

docker compose up -d --build

The first run builds the three local images and stores them in Docker as paperforge/web:local, paperforge/api:local, and paperforge/worker:local. After that, start the existing images with:

pnpm docker:up

When source code or a Dockerfile changes, rebuild and restart with:

pnpm docker:up:build

Useful commands:

pnpm docker:status
pnpm docker:logs
pnpm docker:down

The stack is available at http://localhost:8080. Caddy routes /api/* to Go and all other requests to Next.js. The API, Gotenberg, and Redis do not publish ports to the host. By default, the gateway only listens on 127.0.0.1; configure TLS and optional anonymous rate limiting in the reverse proxy before exposing it to an untrusted network.

Initial API

  • POST /api/v1/jobs: creates a heavy job with multipart/form-data.
  • GET /api/v1/jobs/{id}: returns its current state.
  • GET /api/v1/jobs/{id}/result: downloads the result.
  • DELETE /api/v1/jobs/{id}: cancels or deletes the job.
  • GET /healthz: checks the API and Redis.

Initial server-side operations: ocr, compress, repair, and office-to-pdf.

Languages

The web interface is available at /en and /es. Requests to / are redirected using this priority:

  1. The locale previously selected by the user.
  2. A country code supplied by a trusted GeoIP-aware proxy or hosting provider. Spanish-speaking countries use /es; every other known country uses /en.
  3. The browser's Accept-Language preference when no country code is available.
  4. English for every unknown or unsupported country and language.

Country detection reads x-vercel-ip-country, cf-ipcountry, or x-country-code when available. A self-hosted browser does not reveal the user's country by itself, so deployments without a GeoIP provider fall back to the browser language. Users can always switch languages from the page header.