Self-hosted, source-available alternative to PrintNode. Submit a print job to a small HTTP API from anywhere; an agent on the machine with the printer picks it up and prints it: documents and label printers (Zebra/ZPL, Bixolon, thermal).
Try the live demo: send a print job and click through the real dashboard, in your browser, nothing to install.
- Agent polls outbound, so printers behind NAT/firewall work with no inbound ports
- Server is one small Rust binary: no Python, no Node, nothing else to install (the dashboard is embedded)
- React dashboard (live queue, job history, test print, API keys), REST API, SQLite job queue
- Cross-platform agent, also a single Rust binary: Windows (spooler + SumatraPDF), Linux and macOS (CUPS)
- Print options per job (copies, duplex, paper size, tray, color, page ranges) + capability discovery per printer
- Shop order → packing slip: WooCommerce plugin, Shopify webhook, built-in PDF renderer
- Virtual print server: a
file://printer archives the job to a directory instead of paper (docs) - Multi-tenant: orgs isolate API keys, agents, printers and jobs, each with its own e-mail/password logins for the dashboard (docs)
- Run it as a service: optional self-signup, password reset by e-mail, a per-org monthly job quota (docs), and plans + a signed billing webhook that works with any payment provider (docs)
- Fleet monitoring:
GET /computers+ online/offline webhooks, Prometheus/metrics - Retry-safe submits (
idempotency_key) and job deadlines (expire_after) - PrintNode-compatible API layer: point an existing SDK/integration at your server (docs)
- Star CloudPRNT: Star receipt/label printers poll the server themselves, so there is no agent and nothing installed at the site (docs; built to the published spec, not yet verified on a physical printer)
- Source available (Elastic License 2.0: self-host freely, don't resell it as a service)
Want to look first? The live demo runs the real dashboard in your browser against sample data.
Running v2 (the Python server)? v3 uses the same API, environment and database; see the migration guide for the upgrade, the three stricter defaults and the way back.
Server: download the printpapi-server-<platform> archive from the
latest release (Windows, Linux and macOS;
unpack it, the binary is printpapi):
PRINTAPI_TOKEN=change-me ./printpapiOr build it from source (needs Rust; on Windows the GNU or MSVC toolchain):
git clone https://github.com/LukiPrince/printpapi && cd printpapi
cargo build --release -p printpapi-server
PRINTAPI_TOKEN=change-me ./target/release/printpapiOpen the dashboard at http://localhost:3460 and paste the token. Or use the prebuilt Docker image, no build needed (amd64 + arm64):
docker run -e PRINTAPI_TOKEN=change-me -p 3460:3460 ghcr.io/lukiprince/printpapiOr docker compose up -d, which keeps the DB in a volume (see docker-compose.yml).
Prefer to build it yourself? docker build -t printpapi .
Agent, on the machine with the printers:
-
Download the
printpapi-agent-<platform>archive from the latest release and unpack it (Windows x86-64, Linux x86-64/arm64, macOS Apple silicon/Intel). On Windows putSumatraPDF.exenext to the exe for PDF; Linux and macOS use CUPS (on macOS read the raw-printing note). -
Issue the agent a key on the dashboard's Keys page (or
POST /apikeys), then createagent.ininext to the executable (the archive has anagent.ini.example):[agent] server_url = http://yourserver:3460 api_key = key-from-the-Keys-page name = office-pc printers = Zebra GK420d; HP LaserJet|pdf
-
Run
./printpapi-agent(printpapi-agent.exeon Windows).
First print: dashboard → Devices → Test print.
Locked-down Windows (Smart App Control / WDAC / AppLocker) blocks the agent exe because it is not code-signed yet. There the Python agent is the fallback, see docs/agent.md.
v3 (Rust) against v2.5.1 (the last Python server), measured with
tools/bench/bench.py on Linux (Docker, 16 cores), fresh database each:
| v2.5.1 (Python) | v3 (Rust) | |
|---|---|---|
| Memory, idle | 29.3 MB | 6.6 MB |
| Memory, 200 agents long-polling | 48.8 MB | 10.5 MB |
| Threads, 200 agents long-polling | 203 | 23 |
| CPU, 200 agents long-polling | 3.3 % of a core | 0.8 % |
| Job reaches a waiting agent (p50 / max) | 598 ms / 1021 ms | 19 ms / 52 ms |
| Job submits, 16 clients | 151 /s | 167 /s |
| Agent cycles (claim, payload, result) | 101 jobs/s | 114 jobs/s |
| Docker image | 200 MB | 55 MB |
| Agent memory (idle, on Windows) | 26.2 MB | 8.5 MB |
A waiting agent is woken the moment its job is queued (Python polled the database once a second
per waiting agent). Submit and cycle throughput are bound by the disk: every job state is
committed durably (synchronous=FULL, like v2.5.1), so both servers wait for the same fsyncs. The
server binary is ~8 MB with the dashboard embedded; the agent is ~2.6 MB.
QZ Tray prints from the browser: JavaScript in your page talks to a Java app on the same desktop over a localhost WebSocket. Different architecture, different trade-offs:
| QZ Tray | printpapi | |
|---|---|---|
| Trigger | browser JS on the operator's machine | any HTTP client: backend, n8n/Zapier, cron |
| Silent printing | needs a code-signing certificate per deployment (self-signed → a prompt on every print) | nothing to sign; the agent prints what the server queues |
| Transport | wss://localhost from the page (Chrome's Local Network Access tightens this) |
agent polls outbound, no listener, no localhost anything |
| Headless / service | open since 2016 | systemd / launchd / Task Scheduler (docs) |
| Queue when the PC is off | none (cloud queue open since 2021) | jobs wait in SQLite, print on reconnect (or expire) |
| Direct USB/HID, scales | yes | no, printers only |
QZ Tray is the better fit if the print must start from the user's browser session with no server in the loop. If printing is something your backend or automation does, printpapi's model is simpler: no certificates, no browser, one HTTP call.
PrintNode is the closest match feature-wise, and printpapi is a self-hosted alternative to it: same poll-from-behind-NAT model, same job/printer/computer API shape, without the per-printer monthly fee or sending your documents through someone else's cloud. It has things we don't (scales, a hosted SLA; see the roadmap). Software you already wrote against their API can be pointed here: send HTTP Basic auth instead of a bearer token and the server answers in their JSON shapes (PrintNode-compatible API).
PrintNode is a trademark of PrintNode Ltd. printpapi is not affiliated with, endorsed by, or sponsored by PrintNode.
| Server | how it works, configuration, building, Docker, dashboard (+ rebuilding it), API keys, security |
| Agent | install, agent.ini, printer syntax, labels vs PDF, per-printer setup, service install |
| HTTP API | endpoints, auth, content types, job lifecycle |
| PrintNode-compatible API | point an existing PrintNode client at printpapi |
| Star CloudPRNT | Star printers polling the server directly, without an agent |
| Recipes | print from n8n, Zapier, Make, curl in one HTTP node |
| E-commerce | WooCommerce plugin, Shopify webhook, POST /orders |
| Billing | plans, checkout links, the signed provider webhook, org deletion |
| Migration | upgrading from v2 (Python) to v3 (Rust): server, agents, changed defaults, rollback |
| Roadmap | what shipped and what is next |
Contributions welcome, see CONTRIBUTING.md.
Elastic License 2.0: source available, not OSI open source. In plain terms:
- ✅ Run it for yourself, your company, or your clients' printing, free of charge, commercial use included. Read it, modify it, redistribute it.
- ✅ An IT service provider, integrator or agency may operate it for an identified client and charge for installation, hosting, administration and support (an explicit additional permission on top of ELv2, see the top of LICENSE).
- ❌ Offering it to the public as your own hosted or managed print service. That is the part the author intends to offer.
Two scope notes:
- Everything up to and including v1.4.0 was released under the MIT License and stays MIT (docs/licenses/MIT-until-v1.4.0.txt). The current license applies to later versions.
- The WooCommerce plugin is GPL-2.0-or-later, because WordPress plugins must be GPL-compatible. It only calls the HTTP API.
Not sure whether your use is covered? Open an issue and ask; the answer is almost always yes.

