Back to Blog
DeveloperESC/POSAPI

ESC/POS Printing from the Browser: A Developer's Guide

How to send raw ESC/POS commands from a web app to a thermal printer — covering WebSocket bridges, the Print Agent API, and code examples for React, Shopify, and vanilla JS.

May 8, 202510 min read

The Browser Printing Problem

If you've ever tried to print a receipt from a web app, you know the pain: window.print() opens a print dialog, renders the page as a PDF, and has zero support for ESC/POS commands like auto-cut or custom paper width.

For thermal receipt printers, this is a dealbreaker. You need:

  • Silent printing — no dialog, no user interaction
  • ESC/POS commands — for auto-cut, bold text, alignment, barcodes
  • Reliable encoding — UTF-8 to the correct code page for your printer
  • Cross-platform support — Mac, Windows, Linux

The solution is a local WebSocket bridge — a small background app that runs on the user's computer, accepts print jobs from your web app, and sends raw ESC/POS commands to the printer.


How the WebSocket Bridge Works

`

Your Web App → WebSocket (ws://localhost:8765) → Print Agent → Thermal Printer

`

The flow is simple:

1. Your web app opens a WebSocket connection to ws://localhost:8765

2. You send a JSON payload describing the receipt

3. Print Agent converts it to ESC/POS and sends it to the printer

4. The printer prints silently — no dialog, no driver interaction

MenuForma Print Agent implements this bridge. It's free, open-source, and runs on Mac, Windows, and Linux.


API Reference

Connection

```javascript

const ws = new WebSocket('ws://localhost:8765');

ws.onopen = () => {

console.log('Print Agent connected');

};

ws.onerror = () => {

console.log('Print Agent not running');

};

`

Check Agent Status

Send a ping to verify the agent is running:

```javascript

ws.send(JSON.stringify({ type: 'ping' }));

ws.onmessage = (event) => {

const data = JSON.parse(event.data);

if (data.type === 'pong') {

console.log('Agent version:', data.version);

}

};

`

Print a Receipt

```javascript

const receipt = {

type: 'print',

printer: 'auto', // or specific IP like '192.168.1.100'

data: {

storeName: 'My Restaurant',

storeAddress: '123 Main St',

orderNumber: '#1042',

tableNumber: 'Table 5',

items: [

{ name: 'Margherita Pizza', qty: 1, price: 14.90 },

{ name: 'Sparkling Water', qty: 2, price: 3.50 },

],

subtotal: 21.90,

tax: 1.97,

total: 23.87,

paymentMethod: 'Card',

footer: 'Thank you for dining with us!',

}

};

ws.send(JSON.stringify(receipt));

`


React Integration Example

```javascript

import { useEffect, useRef, useCallback, useState } from 'react';

function usePrintAgent() {

const wsRef = useRef(null);

const [connected, setConnected] = useState(false);

useEffect(() => {

const connect = () => {

const ws = new WebSocket('ws://localhost:8765');

ws.onopen = () => setConnected(true);

ws.onclose = () => {

setConnected(false);

setTimeout(connect, 3000); // retry

};

wsRef.current = ws;

};

connect();

return () => wsRef.current?.close();

}, []);

const print = useCallback((receiptData) => {

if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {

throw new Error('Print Agent not connected');

}

wsRef.current.send(JSON.stringify({ type: 'print', data: receiptData }));

}, []);

return { connected, print };

}

`


Shopify Integration

For Shopify stores, you can trigger printing from an Order webhook or from a browser extension:

```javascript

// In your Shopify storefront or admin extension

async function printShopifyOrder(order) {

const ws = new WebSocket('ws://localhost:8765');

ws.onopen = () => {

ws.send(JSON.stringify({

type: 'print',

data: {

storeName: order.shop_name,

orderNumber: order.name,

items: order.line_items.map(item => ({

name: item.title,

qty: item.quantity,

price: parseFloat(item.price),

})),

total: parseFloat(order.total_price),

paymentMethod: order.payment_gateway,

}

}));

};

}

`


Printer Configuration

The "printer" field in the print payload accepts:

ValueDescription
"auto"Use the printer configured in Print Agent settings
"192.168.1.100"Connect directly to a network printer by IP
"USB"Use the first detected USB printer

Error Handling

```javascript

ws.onmessage = (event) => {

const response = JSON.parse(event.data);

if (response.type === 'print_success') {

console.log('Printed successfully');

} else if (response.type === 'print_error') {

console.error('Print failed:', response.error);

// Show fallback UI or retry

}

};

`


Detecting If Print Agent Is Installed

Before showing print functionality in your UI, check if the agent is running:

```javascript

async function isPrintAgentAvailable() {

return new Promise((resolve) => {

const ws = new WebSocket('ws://localhost:8765');

const timeout = setTimeout(() => {

ws.close();

resolve(false);

}, 1000);

ws.onopen = () => {

clearTimeout(timeout);

ws.close();

resolve(true);

};

ws.onerror = () => {

clearTimeout(timeout);

resolve(false);

};

});

}

// Usage

const agentAvailable = await isPrintAgentAvailable();

if (!agentAvailable) {

// Show download prompt

window.open('/print-agent', '_blank');

}

`


Download Print Agent

MenuForma Print Agent is free and available for Mac, Windows, and Linux. It supports USB, network, and Bluetooth thermal printers, and implements the full WebSocket API described in this guide.

For full API documentation and the open-source code, visit the Print Agent page.

Ready to create your free QR menu?

Join thousands of restaurants using MenuForma. Free to start, no credit card required.