Silent local printing agent for web applications β print PDFs and thermal labels from any web app to any local printer.
PrintBridge is a lightweight local agent that runs in your system tray. Any web application can send print jobs via HTTP to localhost, and PrintBridge prints them silently β no dialog boxes, no user interaction required.
- Silent PDF printing β print PDFs to any system printer without dialogs
- TSPL label printing β native TSPL commands for thermal printers (TSC, Xprinter, Zebra, etc.)
- QR codes β generate QR codes directly in TSPL labels
- Unicode support β automatic bitmap rendering for non-ASCII text (Arabic, Chinese, etc.)
- System tray icon β runs in the background with a minimal UI
- Auto-start β launches on system startup (macOS LaunchAgent / Windows registry)
- Cross-platform β works on macOS, Windows, and Linux
- Configurable β customize port, CORS origins, label defaults, and branding via
config.json - Raw TSPL mode β send arbitrary TSPL commands for full control
git clone https://github.com/AnouarSbia/printbridge.git
cd printbridge
pip install -r requirements.txt
python agent.pyThe agent starts on http://127.0.0.1:9120 and runs in your system tray.
curl http://127.0.0.1:9120/api/status
# {"port":9120,"platform":"Darwin","status":"ok","tspl":true,"version":"1.0.0"}Pre-built binaries are available on the Releases page:
- Windows:
PrintBridge-Setup.exe(installer) orPrintBridge.exe(standalone) - macOS:
PrintBridge-Installer.dmg
No Python installation required for pre-built binaries.
All endpoints accept and return JSON. The agent listens on 127.0.0.1 only (no external access).
Health check β use this to detect if the agent is running.
Response:
{
"status": "ok",
"version": "1.0.0",
"platform": "Darwin",
"port": 9120,
"tspl": true
}List available system printers.
Response:
{
"printers": ["HP LaserJet", "Xprinter XP-350"],
"default": "HP LaserJet"
}Print a PDF document silently.
Request:
{
"pdf": "<base64-encoded PDF bytes>",
"printer": "HP LaserJet",
"copies": 1,
"mediaWidth": 100,
"mediaHeight": 50
}| Field | Type | Required | Description |
|---|---|---|---|
pdf |
string | Yes | Base64-encoded PDF data |
printer |
string | No | Printer name (default: system default) |
copies |
int | No | Number of copies (1-99, default: 1) |
mediaWidth |
number | No | Label width in mm (for thermal printers) |
mediaHeight |
number | No | Label height in mm (for thermal printers) |
Print product labels using TSPL commands (thermal printers).
Request:
{
"labels": [
{
"productName": "Widget A",
"formuleName": "Standard",
"width": 100,
"height": 50,
"position": "Shelf 3",
"quantity": 2,
"orderRef": "#1234",
"clientName": "John Smith",
"measurementId": "M001",
"sig": "abc123"
}
],
"printer": "Xprinter XP-350",
"labelWidth": 100,
"labelHeight": 60,
"shopName": "My Shop",
"workOrderId": 42,
"qrBaseUrl": "https://example.com/label/",
"copies": 1,
"displayOptions": {
"showBarcode": false,
"showFormule": true,
"showClient": true,
"showOrder": true,
"showQr": true,
"showPosition": true,
"showShopHeader": true,
"fontScale": 2
}
}Print compact cut/manufacturing labels using TSPL.
Request:
{
"labels": [
{
"position": "Room 1",
"barInfo": "Bar 1 β Cut 3",
"profileName": "H",
"profileRef": "2RF21",
"cutName": "H",
"finishing": "RAL 7024",
"length": 1200,
"angles": "45/45",
"binNumber": 3,
"source": "production"
}
],
"printer": "Xprinter XP-350",
"labelWidth": 45,
"labelHeight": 35,
"orderRef": "1245",
"copies": 1
}Send raw TSPL commands directly to a printer. Full control for custom label formats.
Request:
{
"tspl": "SIZE 100 mm, 60 mm\r\nGAP 2 mm, 0 mm\r\nDIRECTION 0\r\nCLS\r\nTEXT 10,10,\"4\",0,1,1,\"Hello World\"\r\nQRCODE 400,10,M,6,A,0,\"https://example.com\"\r\nPRINT 1,1\r\n",
"printer": "Xprinter XP-350"
}You can also pass TSPL as an array of command strings:
{
"tspl": [
"SIZE 100 mm, 60 mm",
"GAP 2 mm, 0 mm",
"DIRECTION 0",
"CLS",
"TEXT 10,10,\"4\",0,1,1,\"Hello World\"",
"PRINT 1,1"
],
"printer": null
}Send a simple test label to verify printer connectivity.
Request:
{
"printer": "Xprinter XP-350",
"labelWidth": 100,
"labelHeight": 150
}Edit config.json in the same directory as agent.py:
{
"port": 9120,
"cors_origins": [
"http://localhost:*",
"http://127.0.0.1:*",
"https://your-app.com"
],
"log_dir": "~/.printbridge",
"label_defaults": {
"width_mm": 100,
"height_mm": 150,
"dpmm": 8,
"gap_mm": 2
},
"footer_text": "PrintBridge",
"tray_name": "PrintBridge"
}| Variable | Default | Description |
|---|---|---|
PRINTBRIDGE_PORT |
9120 |
Override the port |
PRINTBRIDGE_FOOTER |
PrintBridge |
Override footer text on labels |
PRINTBRIDGE_TRAY_NAME |
PrintBridge |
Override tray icon name |
By default, PrintBridge only accepts requests from localhost. To allow your web app to connect, add your domain to cors_origins in config.json:
{
"cors_origins": [
"http://localhost:*",
"https://my-app.example.com",
"https://*.my-app.example.com"
]
}- Python 3.10+
pip install -r requirements.txt
build_windows.batProduces:
dist\PrintBridge.exeβ standalone executablePrintBridge-Setup.exeβ NSIS installer (if NSIS is installed)
./build_macos.shProduces:
dist/PrintBridge.appβ macOS app bundledist/PrintBridge-Installer.dmgβ DMG installer (ifcreate-dmgis installed)
ββββββββββββββββ HTTP/JSON βββββββββββββββββ OS print ββββββββββββββ
β Your Web App β βββββββββββββββββββΆβ PrintBridge β βββββββββββββββββΆβ Printer β
β (any domain) β localhost:9120 β (system tray) β lp / win32print β (any type) β
ββββββββββββββββ βββββββββββββββββ ββββββββββββββ
- Your web app sends a POST request to
http://127.0.0.1:9120/api/print(or/api/print-labels,/api/print-tspl) - PrintBridge decodes the data and sends it to the specified printer
- The document prints silently β no dialog, no user interaction
- macOS/Linux: Uses
lpcommand with optional media size for thermal printers - Windows: Uses SumatraPDF (if installed), falls back to PowerShell
Start-Process, thenShellExecute
- macOS/Linux: Uses
lpr -o rawto send raw bytes - Windows: Uses
win32print.WritePrinterwithRAWdata type
// Check if PrintBridge is running
async function checkAgent() {
try {
const res = await fetch('http://127.0.0.1:9120/api/status');
return res.ok;
} catch {
return false;
}
}
// Print a PDF
async function printPdf(pdfBase64, printerName) {
const res = await fetch('http://127.0.0.1:9120/api/print', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
pdf: pdfBase64,
printer: printerName,
copies: 1,
}),
});
return res.json();
}
// Print a custom TSPL label
async function printLabel(tsplCommands, printerName) {
const res = await fetch('http://127.0.0.1:9120/api/print-tspl', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tspl: tsplCommands,
printer: printerName,
}),
});
return res.json();
}| Problem | Solution |
|---|---|
| Agent won't start | Check logs: ~/.printbridge/agent.log |
| Printer not detected | Ensure printer is installed in OS settings |
| Label is blank | Verify label dimensions match your media |
| Port 9120 in use | Change port in config.json or set PRINTBRIDGE_PORT env var |
| CORS error | Add your domain to cors_origins in config.json |
| PDF won't print on Windows | Install SumatraPDF for reliable silent printing |
| Arabic text not rendering | Install arabic-reshaper and python-bidi (included in requirements.txt) |
Contributions are welcome! Please read CONTRIBUTING.md for guidelines.
Areas we'd love help with:
- New printer protocols (ZPL, EPL, CPCL)
- Label template system (JSON-based or visual editor)
- Web UI for configuration
- Linux packaging (deb, rpm, AppImage)
- Unit and integration tests
This project is licensed under the MIT License β see LICENSE for details.
- Flask β web framework
- pystray β system tray icon
- Pillow β image processing for bitmap text rendering
- pywin32 β Windows printer access
- SumatraPDF β silent PDF printing on Windows