printforge by YagnoTech

printforge / guides / print receipts from a web app

Guide

How to print receipts from a web app

You've built the ordering flow, the payment works, and now the kitchen needs a ticket on paper. This is where teams discover that the browser — the platform their whole app lives on — cannot talk to a receipt printer. Here's why, and the three architectures that actually solve it.

Disclosure: we make printforge, the third option below. The first two approaches are legitimate and sometimes the better fit — this guide says so where it's true. printforge launched in 2026.

Why the browser can't just print

Receipt printers are not "printers" in the office sense. A thermal receipt printer is a serial device that speaks ESC/POS — a byte-level command language for feeding paper, styling text, cutting, and kicking open the cash drawer. Getting a receipt out means delivering exact bytes to a USB or network device.

Browsers deliberately can't do that. Web pages can't open raw sockets or write to USB without per-session permission prompts, and window.print() is the opposite of what you want: it pops a dialog, paginates for A4/Letter through the OS driver, and produces something that looks like a fax of your CSS. Silent, unattended printing — the thing a kitchen needs — is exactly what the browser security model forbids.

So every real solution moves the printing out of the page and into a small program running near the printer. The architectures differ only in who talks to that program.

The three architectures

1Local helper, driven by the browser (the QZ Tray pattern)

Install a helper application on each machine that prints. Your page's JavaScript connects to it locally (typically a localhost websocket) and hands over raw ESC/POS or a print job; the helper owns the hardware. QZ Tray is the best-known implementation of this pattern.

Good No cloud in the loop: LAN-fast, keeps printing if the internet drops, and there's no per-print vendor bill (QZ Tray licenses the software instead).

Hard A helper to install, update and certificate-configure on every printing machine; request signing to set up; and printing only happens while a browser tab with your page is open — servers, webhooks and cron jobs can't print.

2Cloud dispatch, driven by your server (the PrintNode pattern)

Flip the direction: your backend posts a job to a cloud queue, and a small agent beside the printer polls that queue outward and prints whatever arrives. The browser is out of the loop entirely — anything that can make an HTTPS request can print: checkout webhooks, mobile apps, scheduled reports.

Good No inbound firewall holes (the agent connects out), central queue with job status and retries, print from anywhere, fleet visible in one dashboard.

Hard Internet required (jobs queue through outages, but paper waits), a vendor in the middle, and a subscription — metered per print and per connected computer on PrintNode's published plans.

3printforge — pattern #2, with flat plans and server-rendered receipts

printforge is a cloud-dispatch implementation with two opinions: plans are flat by job volume (computers are never counted, and printers are uncapped on the top plan), and you shouldn't have to hand-build ESC/POS for a routine receipt — send structured receipt_json and the server renders it to your printer's exact column width. Raw escpos_base64 passthrough and plain text are there when you need them.

The complete printforge integration

Two moving parts. First, the agent — on the machine next to the printer (anything with Node 18+; no installer, no inbound ports). Download it from /agent.js, create a bridge in the dashboard (or POST /v1/bridges) to get its pfb_ device token, then:

ON THE SHOP MACHINENode 18+
# one command — the agent connects outward and long-polls for work
PRINTFORGE_URL=https://printforge.yagnotech.com \
PRINTFORGE_DEVICE_TOKEN=pfb_YOUR_DEVICE_TOKEN \
node agent.js

Second, the job — from your backend, a webhook, or plain curl:

FROM YOUR APPPOST /v1/jobs
curl -X POST https://printforge.yagnotech.com/v1/jobs \
  -H "authorization: Bearer sk_pf_YOUR_KEY" \
  -H "content-type: application/json" \
  -d '{
    "printerId": "YOUR_PRINTER_ID",
    "contentType": "receipt_json",
    "dedupeKey": "order-1042",
    "content": {
      "header": "ACME CAFE\n123 King St W",
      "lines":  [{ "name": "Cheeseburger", "qty": 2, "price": 25.98 },
                 { "name": "Fries",        "qty": 1, "price": 4.50 }],
      "totals": [{ "label": "Subtotal", "amount": 30.48 },
                 { "label": "TOTAL",    "amount": 34.44 }],
      "footer": "Thank you!"
    }
  }'
RESPONSE201 CREATED
{ "job": { "state": "queued", "dedupeKey": "order-1042",  }, "replayed": false }

The agent claims the job, prints it, and acks — the job's state walks queued → dispatched → printed (or failed, with the reason and up to 3 automatic retries). Two details worth stealing for any implementation of pattern #2:

  • Idempotent submits. The dedupeKey ties the job to your order ID: if your webhook fires twice, the second call replays the first job ("replayed": true) instead of printing the receipt twice. Checkout code retries; kitchens shouldn't see it.
  • Outbound-only agents. The agent long-polls GET /bridge/v1/jobs?wait=25 over HTTPS. Nothing ever dials into the shop network, so there's no port-forwarding conversation with anyone's router.

Which one should you pick?

  • Offline-first, browser-driven, no vendor: the local-helper pattern (#1). Budget for installing and maintaining the helper on every till, and accept that only open browser sessions can print.
  • Server-driven printing at fleet scale: cloud dispatch (#2/#3). If your workload is PDFs to driver-installed printers, PrintNode's client-side rendering is the stronger fit; if it's receipts and tickets across many machines, that's the shape printforge was built for — the printforge vs PrintNode comparison puts the published numbers side by side.
  • Just exploring? The live demo runs this exact pipeline against a virtual printer in your browser — no signup, nothing to install.

14-DAY FREE TRIAL · NO CARD

From signup to paper in about ten minutes.

The dashboard's guided first print walks you through the bridge, the printer and a test page — then the curl above is your whole integration.