API documentation
PrintTrays exposes a JSON-over-WebSocket protocol on wss://127.0.0.1:8181 (with automatic fallback ports), plus a thin client SDK that wraps it.
Getting started
Four steps, start to finish.
- Install PrintTrays on the machine that has the printer — see Download. It runs quietly in the system tray from then on, starting automatically at login.
- Include the client SDK in your page, as a native ES module (no build step, no bundler required):
<script type="module"> import { PrintTrays } from "https://your-host.example.com/printtrays.js"; </script> - Connect — this is also the point where, the very first time on a given browser profile, the user may need to click through a one-time certificate trust step (PrintTrays opens this as a small self-closing popup rather than sending the user to a separate tab):
const pp = new PrintTrays(); await pp.connect(); console.log("Connected to bridge v" + pp.version); - Print:
const printers = await pp.getPrinters(); await pp.print(printers[0].name, "<h1>Hello</h1>", { type: "html" });
Client SDK
import { PrintTrays } from "./printtrays.js";
const pp = new PrintTrays({
host: "127.0.0.1", // default
port: undefined, // default: tries 8181, then 18181, then 28181
connectTimeoutMs: 2500,
downloadUrl: "https://your-site.example.com/download",
});
await pp.connect();| Method | Description |
|---|---|
connect() | Opens the WSS connection (trying each fallback port in turn), pins the bridge's signing key on first use, and sets pp.version to the connected bridge's version string. Throws PrintTraysNotInstalledError if unreachable. |
getPrinters() | Resolves to { name, isDefault, status, driver }[] for every OS-installed printer. |
print(printer, data, options) | options.type is one of pdf | html | raw | zpl | escpos. See below. |
sendRaw(port, bytes, options?) | Raw serial/HID/USB/TCP write (and optionally read). port is "COM3", "tcp:host:port", "hid:<path>", or "usb:<vid>:<pid>:<endpoint>". options.readTimeoutMs reads a response back (base64, in the result); leave bytes empty for a read-only request. Serial-only options: baudRate, dataBits, stopBits, parity, rtscts, xon, xoff. |
usbControlTransfer(params) | Low-level USB control transfer for device configuration/status exchanges outside normal data endpoints. params.data (base64) for an OUT transfer, or params.readLength for an IN transfer. |
listDevices(type) | type is usb | serial | hid. |
disconnect() | Closes the socket. |
Wire protocol
Every request is a single JSON object with an id and action; every response mirrors that id.
listPrinters
// →
{ "id": "abc", "action": "listPrinters" }
// ←
{ "id": "abc", "action": "listPrinters", "ok": true,
"result": [{ "name": "EPSON TM-T88", "isDefault": true }],
"timestamp": 1234567890, "signature": "...", "keyFingerprint": "..." }{
"id": "abc",
"action": "print",
"printer": "EPSON TM-T88",
"type": "escpos", // pdf | html | raw | zpl | escpos
"data": "...", // utf8 text, or base64 if binary
"encoding": "base64", // optional, defaults to utf8
"copies": 1, // optional
"connection": "tcp:192.168.1.50:9100", // optional: bypass OS spooler
// The following only apply to type "pdf"/"html":
"duplex": "longEdge", // optional: simplex | shortEdge | longEdge
"landscape": true, // optional
"paperSize": "A4", // optional: A3 | A4 | A5 | Legal | Letter | Tabloid
"color": false, // optional: defaults to true
"scaleFactor": 100, // optional: percentage
"margins": { "marginType": "none" } // optional
}rawWrite
{
"id": "abc",
"action": "rawWrite",
"port": "COM3", // or "/dev/ttyUSB0", "tcp:host:port",
// "hid:<path>", or "usb:<vidHex>:<pidHex>:<endpoint>"
"bytes": "...", // leave "" for a read-only request
"encoding": "utf8", // utf8 | base64 | hex
"readTimeoutMs": 500, // optional: read a response back (base64)
"readLength": 64, // optional, usb: ports only
// Serial ports only, beyond baudRate:
"baudRate": 9600,
"dataBits": 8, // 5 | 6 | 7 | 8
"stopBits": 1, // 1 | 1.5 | 2
"parity": "none", // none | even | mark | odd | space
"rtscts": false,
"xon": false,
"xoff": false
}usbControlTransfer
{
"id": "abc",
"action": "usbControlTransfer",
"vendorId": 1155,
"productId": 22352,
"bmRequestType": 33,
"bRequest": 9,
"wValue": 512,
"wIndex": 0,
"data": "..." // base64, for an OUT transfer
// or: "readLength": 8 // for an IN transfer instead
}listDevices
{ "id": "abc", "action": "listDevices", "type": "serial" } // usb | serial | hidTrust & signing
How PrintTrays avoids QZ Tray-style repeated certificate prompts.
- On first run, PrintTrays generates a local CA + TLS leaf certificate (see
server/src/certManager.ts). The installer imports the CA into the OS trust store, so the WSS handshake itself is trusted by the browser. - Independently, every response is signed with a separate RSA keypair (
server/src/signing.ts). The client SDK verifies that signature with WebCrypto and pins the key's fingerprint inlocalStorageafter the first connection — this is what lets a page assert "this really is my local PrintTrays install," not just "some TLS server on port 8181." - If PrintTrays is reinstalled, its signing key changes — the SDK detects this and asks once via a confirmation prompt rather than silently trusting a different identity or dead-ending with no way to recover.
Roadmap
PrintTrays now covers essentially the full QZ Tray feature set — silent printing with full page-setup options, raw ESC/POS/ZPL, and USB/HID/serial device I/O including control transfers and configurable serial parameters. On the platform side:
| Status | Details |
|---|---|
| Windows | Built and tested end-to-end across multiple real machines |
| Linux (.deb / .AppImage) | Built and functionally verified in a real Linux container: launches, generates its certificate, binds the bridge, and a real WebSocket client completed a full signed round-trip. Only the desktop tray icon and the .deb installer's certificate-import script remain unverified on real hardware |
| macOS | Confirmed hard-blocked (not assumed): .pkg fails on a missing Apple-only binary (productbuild), and .dmg's dependency explicitly restricts itself to macOS at the npm level. Needs real Mac hardware or cloud Mac CI to proceed |
See QZ-TRAY-FEATURE-PARITY.md in the project source for the full, item-by-item comparison.