# How to Print Receipts from a Web App Without Bluetooth

> Published: 2026-11-10
> Updated: 2026-11-10
> Author: Mathias Nielsen
> Canonical: https://finalpos.com/blog/print-receipt-from-web-app

Browsers block Web Bluetooth on iOS and ruin thermal receipt layouts with slow print dialogs. Learn how to print receipts from a web app over a network using WebPRNT, ePOS-Print, cloud queues, or a native station wrapper.

You just spent a weekend building a custom checkout in a browser. The UI is sharp, cart calculations are instant, and then you test it on an iPad with a receipt printer. Two things break immediately: Safari blocks Web Bluetooth completely, and calling `window.print()` pops up an iOS system modal that takes five seconds to load, ruins the 80 mm thermal layout, and forces the cashier to tap twice on every single coffee order.

This hardware wall stops most web-based POS prototypes from ever reaching a live counter. A browser sandbox is built to stop websites from silently controlling local peripherals or firing raw ESC/POS byte commands over a local network. If you want to print receipt from web app flows without Bluetooth, you have to work around the browser's security model.

We cover the full peripheral stack in our guide to [POS Hardware for Custom Apps: Printers, Drawers, Scanners, Displays](/blog/pos-hardware-for-custom-apps). Here is the engineering breakdown for getting thermal paper out of a printer from a web app, what breaks in production, and how we bypass the browser problem entirely.

## Browser Print Dialogs Fail at the Counter

Standard browser printing relies on the operating system's print spooler, which expects an 8.5 x 11 inch (or A4) page. Call `window.print()` in Chrome or Safari and the browser pauses execution, renders the DOM into a paginated PDF preview, and waits for a human to confirm the printer, paper size, and margins.

![Thermal receipt printer feeding an excessively long blank paper roll beside a tablet showing a print dialog modal](https://storage.googleapis.com/final-os-media/media/43399b6a-0d29-48b6-84dd-88ef01fcb193/generated/5a95dba308614e02-print-receipt-from-web-app-section-1-en-us.png)
*Standard browser print dialogs expect full-size pages, often ignoring variable height CSS and feeding extra blank thermal paper before firing the cutter.*


At a busy retail or restaurant counter, that workflow breaks down fast:

* **Speed and extra taps:** A cashier ringing up a line of twenty customers can't tap "Print" in the web app, wait two seconds for the iOS or Windows print preview to render, and tap "Print" again in the system dialog.
* **Page scaling and margins:** Browsers automatically inject headers, footers (the URL and date), and default margins. Even with `@page { size: 80mm auto; margin: 0; }` in your CSS, mobile browsers routinely ignore variable height rules and feed an extra six inches of blank thermal paper before firing the cutter.
* **No hardware control codes:** A standard PDF print job can't easily send the raw ESC/POS `0x1B 0x70` pulse command required to kick open the RJ12 cash drawer connected to the back of the printer.

Desktop Chrome has a `--kiosk-printing` command-line launch flag that suppresses the confirmation dialog and sends the print job straight to the default printer. That works if you control a dedicated Windows PC at the counter, though you still lose reliable cash drawer kicks and accurate paper cutter control. On an iPad or Android tablet running a mobile browser, kiosk flags don't exist.

## How Do WebPRNT and ePOS-Print Work Over a Local Network?

Direct network printing from a browser works by sending an HTTP POST request containing XML or JSON payloads directly from client JavaScript to an embedded web server running inside a LAN or Wi-Fi thermal printer. The printer skips the operating system's print dialog, parses the payload on-board, and drives the thermal head and cash drawer directly.

![Close-up of an Ethernet LAN cable and RJ12 cash drawer cable plugged into the back of a network receipt printer](https://storage.googleapis.com/final-os-media/media/43399b6a-0d29-48b6-84dd-88ef01fcb193/generated/9adda3061257f506-print-receipt-from-web-app-section-2-en-us.png)
*Direct network printing protocols like Star WebPRNT and Epson ePOS-Print run an embedded HTTP server on the printer's Ethernet or Wi-Fi interface.*


**TL;DR:** Star WebPRNT and Epson ePOS-Print let your web app POST print jobs straight to the printer's local IP address over HTTP/HTTPS, though modern browser mixed-content and local network rules make TLS setup mandatory.

Because Apple refuses to support the Web Bluetooth API in Safari or anywhere on iOS WebKit, you can't connect a web app to a Bluetooth printer on an iPad. Still deciding on hardware connectivity? Read our breakdown on [Bluetooth, BLE or Network: Choosing a Receipt Printer for an iPad POS](/blog/bluetooth-vs-network-receipt-printer). For a browser app, Ethernet or Wi-Fi is your only direct local path.

Printer manufacturers solved this by putting lightweight HTTP servers onto the network interface cards of their printers:

1. **Star Micronics WebPRNT:** Supported on models like the Star mC-Print3 and TSP654II (with the WebPRNT interface card). You construct an XML string using Star's JS SDK (`StarWebPrintBuilder`) and send it via `fetch` or `XMLHttpRequest` to `http://<printer-ip>/StarWebPRNT/SendMessage`. We detail the native and web setup in [How to Connect a Star Micronics Printer to Your Own POS App](/blog/star-micronics-printer-custom-app).
2. **Epson ePOS-Print:** Built into Epson's TM-m30III and TM-T88VII series. Your web app sends a SOAP/XML payload (or uses Epson's `epos-2.x.x.js` SDK) to port 8008 (HTTP) or 8043 (HTTPS) on the printer's local IP. We cover the exact API payloads in [How to Print to an Epson TM Printer from a Custom POS](/blog/epson-epos-sdk-custom-pos).

### The HTTPS Mixed-Content Trap

Five years ago, local HTTP POST printing was easy. Today, your production web POS is served over `https://`. When an `https://` web app tries to send a `fetch` request to `http://192.168.1.50:8008`, Chrome and Safari block it immediately under Mixed Content security rules.

To fix this, you have to enable HTTPS on the printer itself. Because the printer lives on a local IP like `192.168.1.50`, it uses a self-signed SSL certificate that browsers don't trust by default. Your setup checklist for every single store becomes:

* Assign a static IP lease to the printer's MAC address in the store router so DHCP never moves it.
* Log into the printer's web configuration page, generate a self-signed certificate (or load a custom root CA), and download it to every iPad or PC at the counter.
* On iOS, install the profile, go to Settings > General > About > Certificate Trust Settings, and manually toggle on full trust for that specific printer IP.
* On Chrome (version 142 and newer), account for Private Network Access (PNA) preflight checks, which require the printer's firmware to respond with the `Access-Control-Allow-Private-Network: true` header.

If the merchant resets their router and the subnet changes from `192.168.1.x` to `192.168.0.x`, the certificate breaks and printing stops.

## Cloud Printing Services and Their Latency Tradeoffs

When local HTTPS certificates get too brittle to manage across multiple stores, developers usually look at cloud print queues. You have two main routes here.

![Overhead flat-lay comparing 80mm and 58mm thermal receipt paper rolls alongside a brass ruler](https://storage.googleapis.com/final-os-media/media/43399b6a-0d29-48b6-84dd-88ef01fcb193/generated/00fe86964dd8989d-print-receipt-from-web-app-section-3-en-us.png)
*Thermal print heads run at fixed dot densities (typically 203 DPI), requiring precise character padding and 1-bit monochrome dithering for 80 mm and 58 mm rolls.*


You can use manufacturer-native cloud polling, like **Star CloudPRNT** or **Epson Server Direct Print**. Instead of the browser talking to the printer on the LAN, you configure the printer to poll an HTTPS endpoint on your backend server every few seconds. When a cashier completes an order in the web app, your backend queues the receipt job. The next time the printer polls your server, it downloads the payload, prints the receipt, and sends an HTTP confirmation back.

Or you can use third-party desktop relay services like PrintNode or QZ Tray, where your cloud backend pushes a job to a local daemon running on a computer in the shop.

Cloud polling eliminates CORS, Mixed Content, and iPad SSL certificate installation because the browser only talks to your standard `https://` backend while the printer initiates an outbound connection to your server.

The catch is latency and internet dependence. A direct LAN print fires in 150 milliseconds, whereas a CloudPRNT job takes **2 to 5 seconds** depending on the polling interval and TLS handshake overhead. Three seconds sounds trivial until a bartender is waiting for the cash drawer to pop open between every drink order. Worse, if the store's internet connection drops, a pure cloud-polling setup can't open the cash drawer or print a kitchen ticket—even when the local Wi-Fi network is still running fine.

## Helper Apps and Custom URL Schemes

If you need sub-second local printing from a mobile browser without managing self-signed SSL certificates on every printer, you can route print jobs through a native companion app installed on the same iOS or Android device.

Printer manufacturers build utilities like **Star PassPRNT** for this exact bottleneck. When the cashier taps Print in your web app:

1. Your JavaScript encodes the receipt layout or markup into a URL-safe string.
2. The browser redirects to a custom URL scheme, such as `starpassprnt://v1/print/nopreview?back=https://yourpos.com/checkout&html=...`.
3. iOS switches out of Safari, opens the native Star PassPRNT app in the foreground, fires the print job over LAN or Bluetooth (since the native helper app has Apple MFi approval), and automatically redirects back to your web app URL via the `back` parameter.

It works without certificates and even unlocks Bluetooth printers for web apps. The user experience, though, is jarring. Every print triggers an app-switch animation on the iPad, briefly leaving the browser, flashing a native utility screen, and reloading or returning to the web page. If your web app doesn't persist state cleanly across URL redirects, the cashier loses their place.

On desktop Windows or macOS machines, **QZ Tray** handles this more cleanly by running a background Java WebSocket server on `localhost` (bundled with its own trusted root certificate installer), letting the browser stay in the foreground while pushing raw ESC/POS bytes to USB or network printers.

## Formatting Receipts: Paper Widths, Logos, and QR Codes

Once you establish a communication pipe to the printer, you can't just send flexbox HTML and expect a crisp receipt. Thermal printers have fixed dot-density print heads, normally **203 DPI (8 dots per mm)**.

Retail and hospitality rely on two standard thermal paper widths:

* **80 mm (3.125 inches):** The standard for stationary counter and kitchen printers. The actual printable area is usually **72 mm (576 dots)** across. In standard Font A (12 x 24 dots per character), you get **42 or 48 characters per line** depending on the model.
* **58 mm (2.25 inches):** Common on portable belt printers and compact all-in-one payment terminals. The printable width is **48 mm (384 dots)**, giving you **32 characters per line** in Font A.

Print an HTML canvas rendered as a raster image and your receipt will crawl out of the machine with fuzzy text whenever the canvas pixel width doesn't map 1:1 to 576 or 384 dots. Native command builders (like WebPRNT XML or ESC/POS byte arrays) print text using the printer's internal hardware fonts. That drives the stepper motor at full speed—up to 250 mm to 350 mm per second—and keeps text razor-sharp.

Three formatting rules save a lot of headaches on thermal hardware:

1. **Pad strings for two-column alignment:** Thermal hardware fonts are monospace. To put an item name on the left and a price on the right on a 48-character line, count the string lengths and pad the middle with spaces (`"Latte".padEnd(40, " ") + "$4.50".padStart(8, " ")`).
2. **Dither logos to 1-bit monochrome:** Thermal paper only turns black where heated; there is no true grayscale. Convert merchant logos to a 1-bit monochrome bitmap (using Floyd-Steinberg or Atkinson dithering) constrained to roughly 384 dots wide before converting to base64.
3. **Use native barcode and QR commands:** Don't generate a QR code as a PNG in the browser and send it as an image if you can avoid it. Dithering artifacts make small QR codes unreadable. Send the raw URL string inside a native `<qrcode>` or ESC/POS `GS ( k` command so the printer's firmware renders the modules cleanly at native dot resolution.

## Solving Receipt Printing in Final Without Browser Workarounds

Building a custom checkout flow with AI or modern web tools takes minutes, right up until you lose two weeks debugging iOS certificate trust settings, CORS preflights, and 48-column ASCII padding.

We built Final so makers and merchants never have to write printer drivers or fight browser sandboxes. Final is a customizable POS where you describe your checkout workflow in Flow Studio, or start from a ready-made flow in the Flow Library and adjust every screen, rule, and button to match how the store actually runs. Underneath your custom interface, the commerce rails and hardware integrations are already wired.

Instead of stranding your custom flow inside a restricted browser tab, you deploy it with **Run** to Final Station on iOS, Android, macOS, or Windows. Our help guides on [Native Station vs. Virtual Station](https://finalpos.com/help/native-vs-virtual-station), [How to connect a Printer](https://finalpos.com/help/connect-a-printer), and [How to set up multi-printer routing on a Station](https://finalpos.com/help/how-to-set-up-multi-printer-routing-on-a-station) break down how this works:

* **Virtual Station in a web browser:** Test flows immediately and print single receipts using the browser's built-in print dialog (multi-printer routing isn't available in the browser).
* **Native Station:** The exact same custom flow runs inside the native Final Station app on your iPad, Android tablet, desktop, or dedicated hardware like Final Touch Pro and Final Flex (including the Flex Restaurant and Flex Retail desktop or floor stands with built-in receipt printers). Because the Native Station talks directly to hardware at the OS level, it discovers network and Bluetooth receipt printers automatically, lets you pick your paper size, prints receipts automatically when an order completes, triggers the cash drawer on a sale, and handles multi-printer routing for kitchen and bar tickets.

Our [Quick Register order and receipt tools](https://finalpos.com/help/how-to-process-refunds-print-receipts-and-manage-orders-in-quick-register) also handle thermal printing on order completion and let staff reprint from the Order Detail page if a printer goes offline or runs out of paper mid-shift. Offline receipt queueing isn't supported yet, so the sale records safely while you reload the roll.

Skip Safari's local network blocks and self-signed printer certificates. Open Flow Studio, customize your checkout flow on our free tier (with 1,000 free build credits a day, up to 5,000 a month, and zero monthly software fees), and deploy it to Final Station for instant, silent receipt printing on the hardware you already own.

## FAQ

**Q: Can a browser print to a receipt printer?**
A: Yes. A web browser can print silently to network-connected thermal printers that run an embedded HTTP server, like Star WebPRNT and Epson ePOS-Print models. Your web app sends an XML or JSON payload via an HTTPS POST request straight to the printer's local IP address, skipping the slow OS print dialog.

**Q: Why can my web app not print over Bluetooth on iPad?**
A: Apple doesn't support the Web Bluetooth API in Safari or any browser on iOS and iPadOS. Because every iPad browser has to use Apple's WebKit engine, a pure web app can't discover or send data to Bluetooth Classic or BLE receipt printers without handing the job off to a native iOS app.

**Q: What is the standard receipt paper width?**
A: Stationary countertop and kitchen POS printers use 80 mm (3.125-inch) thermal paper, which gives you a 72 mm (576-dot) printable area and fits 42 to 48 monospace characters per line. Compact mobile and handheld printers usually take narrower 58 mm (2.25-inch) rolls, which fit 32 characters per line.