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:
| Value | Description |
|---|---|
| "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.