A lightweight Windows background service that exposes a local HTTP API, enabling web apps to print HTML silently — no browser print dialog, no user interruption.
When a user hits Print in a web app, the browser always opens a print dialog. For most cases that's fine — but for high-frequency, repetitive printing (barcode label scanning, receipt printing, shipping labels) it becomes a serious friction point. Every scan → every print → dismiss dialog. Over hundreds of prints a day, this is painful.
silent-print-service runs as a Windows Service on the user's machine. Your web app calls its local HTTP API instead of triggering the browser's print dialog. The service routes the job directly to the right physical printer based on paper size — no dialog, no clicks, just print.
Web App ──POST /api/print──▶ silent-print-service ──▶ Windows Printer
{ html, paper_size } (localhost) (silent)
- Silent printing — HTML content is sent directly to a configured Windows printer
- Paper size routing — map each paper size (A4, A6, Label, etc.) to a specific printer
- Runs as a Windows Service — starts automatically on boot, no console window
- Built-in settings UI — configure printer mappings via a browser at
http://localhost:8123 - REST API — simple JSON API for health checks, printer discovery, config, and printing
- CORS enabled — call from any web origin
- Windows 10 / Windows Server 2016 or newer
- PowerShell 5.1+ (included in Windows)
- Internet Explorer COM object (present on all standard Windows installs)
- Go 1.21+ (only if building from source)
- Go to Releases and download the latest
silent-print-service.exe - Place it in a permanent folder, e.g.
C:\PrintService\ - Open PowerShell as Administrator in that folder and run:
.\silent-print-service.exe -service install
.\silent-print-service.exe -service start- Open your browser at http://localhost:8123 to configure your printer mappings.
go install github.com/yourusername/silent-print-service@latestThen run the same install / start commands above.
git clone https://github.com/yourusername/silent-print-service.git
cd silent-print-service
go build -o silent-print-service.exe .All commands require Administrator privileges.
| Command | Description |
|---|---|
-service install |
Register as a Windows Service (auto-start) |
-service uninstall |
Remove the service |
-service start |
Start the service |
-service stop |
Stop the service |
-service restart |
Restart the service |
# Example — stop and uninstall
.\silent-print-service.exe -service stop
.\silent-print-service.exe -service uninstallOn first run, a config.json file is created next to the executable:
{
"port": 8123,
"printer_mappings": {
"A4": "HP LaserJet Pro M404n",
"A6": "Zebra ZD421 (Label Printer)"
}
}You can edit this file directly or use the web UI at http://localhost:8123.
| Key | Dimensions |
|---|---|
A3 |
297 × 420 mm |
A4 |
210 × 297 mm |
A5 |
148 × 210 mm |
A6 |
105 × 148 mm |
Letter |
216 × 279 mm |
Legal |
216 × 356 mm |
Base URL: http://localhost:8123
All endpoints return JSON in the format:
{ "success": true, "data": { ... } }Returns service status and uptime.
curl http://localhost:8123/api/health{
"success": true,
"data": {
"status": "healthy",
"uptime": "3h12m4s",
"version": "1.0.0",
"platform": "windows/amd64"
}
}Lists all printers installed on the machine.
curl http://localhost:8123/api/printers{
"success": true,
"data": [
{
"name": "Zebra ZD421",
"driver_name": "ZDesigner ZD421-300dpi ZPL",
"port_name": "USB001",
"status": "Ready",
"is_default": false
}
]
}Returns current port and printer mappings.
Updates printer mappings. Send an empty string to remove a mapping.
curl -X POST http://localhost:8123/api/config \
-H "Content-Type: application/json" \
-d '{
"mappings": {
"A4": "HP LaserJet",
"A6": "Zebra ZD421",
"A3": ""
}
}'Sends an HTML document to the printer configured for the given paper size.
curl -X POST http://localhost:8123/api/print \
-H "Content-Type: application/json" \
-d '{
"html": "<h1>Order #1042</h1><p>Ship to: John Doe</p>",
"paper_size": "A6"
}'Request body:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
html |
string | ✅ | — | Full HTML content to print |
paper_size |
string | ❌ | A4 |
One of the supported paper sizes |
Success response:
{
"success": true,
"message": "Print job sent successfully",
"data": {
"printer": "Zebra ZD421",
"paper_size": "A6"
}
}async function silentPrint(html, paperSize = 'A4') {
const res = await fetch('http://localhost:8123/api/print', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ html, paper_size: paperSize }),
});
const data = await res.json();
if (!data.success) throw new Error(data.error);
return data;
}
// Usage
await silentPrint('<h1>Label</h1><p>SKU: 00123</p>', 'A6');import { useState, useCallback } from 'react';
const PRINT_SERVICE_URL = 'http://localhost:8123';
export function useSilentPrint() {
const [printing, setPrinting] = useState(false);
const [error, setError] = useState<string | null>(null);
const print = useCallback(async (html: string, paperSize = 'A4') => {
setPrinting(true);
setError(null);
try {
const res = await fetch(`${PRINT_SERVICE_URL}/api/print`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ html, paper_size: paperSize }),
});
const data = await res.json();
if (!data.success) throw new Error(data.error);
return data;
} catch (err: any) {
setError(err.message);
throw err;
} finally {
setPrinting(false);
}
}, []);
return { print, printing, error };
}// In your component
function BarcodeScanner() {
const { print, printing, error } = useSilentPrint();
const handleScan = async (barcode: string) => {
const label = `
<div style="font-family: monospace; padding: 8px;">
<h2 style="margin: 0;">Item Received</h2>
<p>Barcode: <strong>${barcode}</strong></p>
<p>${new Date().toLocaleString()}</p>
</div>
`;
await print(label, 'A6');
};
return (
<div>
{printing && <span>Printing...</span>}
{error && <span style={{ color: 'red' }}>{error}</span>}
{/* your scanner UI */}
</div>
);
}If the service is not running, fall back to the browser print dialog:
async function printWithFallback(html, paperSize = 'A4') {
try {
const health = await fetch('http://localhost:8123/api/health', { signal: AbortSignal.timeout(500) });
if (health.ok) {
return silentPrint(html, paperSize);
}
} catch {
// service unavailable — fall back
}
// Browser fallback
const win = window.open('', '_blank');
win.document.write(html);
win.print();
win.close();
}Navigate to http://localhost:8123 in any browser on the machine to:
- View all installed printers and their status
- Map each paper size to a specific printer
- Send test print jobs
silent-print-service/
├── main.go # Windows Service lifecycle (install/start/stop)
├── server.go # HTTP server and route registration
├── handlers.go # API request handlers
├── printer_windows.go # Windows-specific printer enumeration & printing
├── config.go # Config load/save (config.json)
├── models.go # Shared types (PaperSize, PrinterInfo)
└── web/
└── templates/
└── settings.html
Printing is implemented via PowerShell's Internet Explorer COM object, which sends the job directly to the Windows print spooler without any UI.
Current limitations:
- Windows only (uses
winspool.drvand IE COM object) - Printing relies on Internet Explorer COM — may be affected on systems with IE fully removed
Planned / community welcome:
- macOS support (
lp/ CUPS) - Linux support
- Replace IE COM with a headless Chromium/WebView2 backend
- Per-printer CSS page size injection
- Print job queue and status tracking
- Authentication / API key support for multi-user environments
MIT — see LICENSE
Issues and PRs are welcome. If you're adding support for a new OS, see printer_windows.go for the interface to implement.