A simple and efficient ESC/POS printer server built with PHP, Workerman and ESC/POS-PHP, providing WebSocket-based printing capabilities for thermal printers.
- Cross-platform: Works on Linux, macOS, and Windows.
- Multi-interface printer support:
- CUPS
- Ethernet
- Linux-USB
- SMB
- Windows-USB
- Windows-LPT
- WebSocket Server for real-time receipt printing.
- Built-in Web GUI configuration accessible via HTTP.
- Customizable receipt templates (Epson or custom).
- Cash drawer control using ESC/POS pulse command.
- Structured JSON configuration for easy setup.
- Logging system with rotating daily logs.
- Printer testing utility.
- Bundled Windows service installer (via NSSM).
The eps (ESC/POS Printer Server) executable bridges your web or desktop application and the thermal printer.
- The client app (browser, POS app, or API) connects via WebSocket.
- The EPS server receives JSON payloads and translates them to ESC/POS commands.
- Commands are sent to the connected printer via the configured interface.
- Download
eps-windows-bundlearchive from the Release Page - Extract somewhere on your system
- Open directory or extracted content
- Double Click
eps-install.batto install and run ESC/POS Printer Server as a Windows Services - Need to uninstall? double click
eps-uninstall.batto stop and remove ESC/POS Printer Server from Windows Service
Requirement
- PHP >= 8.3 (Installed)
- PHP Extensions:
- sockets
- intl
- openssl
Installation Process
- Download
eps-unix-bundlearchive from the Release Page - Extract somewhere on your system
- Open directory or extracted content
- See usage section
git clone https://github.com/darkterminal/escpos-printer-server.git
cd escpos-printer-servercomposer installcomposer run create:pharNote: Phar is generated inside
bindirectory
ESC/POS Printer Server is already installed on Windows Service Manager.
- Open your terminal inside extracted content
- the run this command
php eps.phar startorphp eps.phar start -dto run as a daemon.
- Web GUI on:
http://localhost:1100 - ESC/POS WebSocket Server:
ws://localhost:1945
The WebSocket server expects a single JSON object as the print job request. This object contains the print source, printer configuration, and receipt data.
{
"from": "posclient",
"printer_name": "IW-8001",
"printer_settings": { ... },
"receipt_data": { ... }
}Top-Level Fields
| Key | Type | Required | Description |
|---|---|---|---|
from |
string |
Yes | Identifier for the client or request source. Used to distinguish between standard printing, test prints, or cash drawer triggers. Examples: "posclient", "testprinter", "pullcashdrawer". |
printer_name |
string |
Yes | The system-recognized printer name. On Linux (CUPS) it matches the printer queue name; on Windows it matches the printer’s registered device name. |
printer_settings |
object |
Yes | Printer configuration and behavior options. Defines how the server connects to the printer, layout preferences, and text settings. |
receipt_data |
object |
Yes | Contains all data that will be printed on the receipt, including header, items, totals, and optional promotional or footer sections. |
Printer Settings (printer_settings)
| Key | Type | Required | Description |
|---|---|---|---|
interface |
string |
Yes | Communication interface for the printer. Supported values: "ethernet", "cups", "linux-usb", "windows-usb", "windows-lpt", "smb". |
printer_host |
string |
Optional | Hostname or IP address of the printer (used only for network/ethernet printers). |
printer_port |
integer |
Optional | TCP port number for the printer (default: 9100). |
template |
string |
Yes | Printer layout template type. Supported: "epson" or "custom". Determines font sizes and column widths. |
pull_cash_drawer |
boolean |
Optional | When true, sends a pulse signal to open the cash drawer after printing. |
line_feed_each_in_items |
integer |
Optional | Number of line feeds (empty lines) after each item in the cart. Default: 1. |
more_new_line |
integer |
Optional | Adds additional empty lines between sections to increase spacing (useful for custom templates). |
custom_print_header |
array[string] |
Optional | Custom header lines (store info, slogan, etc.). Each array element represents one printed line. |
custom_print_footer |
array[string] |
Optional | Custom footer lines (thank-you message, return policy, etc.). |
custom_language |
object |
Optional | Overrides the default labels for receipt fields (e.g., “Operator”, “Time”, “Total”). See the next section for supported keys. |
Supported Custom Language Keys
| Key | Description |
|---|---|
operator |
Label for the cashier/operator name |
time |
Label for transaction time |
transaction_number |
Label for transaction ID |
customer_name |
Label for customer name |
tax |
Label for tax amount |
member |
Label for member discount |
total |
Label for total transaction amount |
paid |
Label for amount paid |
return |
Label for change amount |
due_date |
Label for due date (used in credit transactions) |
saving |
Label for customer deposit |
loan |
Label for loan amount (in credit purchases) |
Receipt Data (receipt_data)
This object defines the actual content of the printed receipt.
Each field maps to a specific section printed by ReceiptPrinter.
| Key | Type | Required | Description |
|---|---|---|---|
app_name |
string |
Yes | Store or POS system name displayed in the header. |
store_address |
string |
Optional | Physical store address printed under the store name. If missing, defaults to “Default Address”. |
full_name |
string |
Yes | Name of the cashier/operator processing the transaction. |
transaction |
object |
Yes | Transaction-level information such as transaction number, total, and payment details. |
cart |
array[object] |
Yes | List of purchased items, each containing name, price, quantity, and subtotal. |
promo |
array[string] |
Optional | Promotional text or campaign messages printed near the footer. |
Transaction Object (receipt_data.transaction)
| Key | Type | Required | Description |
|---|---|---|---|
date_transaction |
string (YYYY-MM-DD) |
Yes | Date of the transaction. Used for time-stamping the receipt. |
transaction |
string |
Yes | Unique transaction number or invoice code. |
customer_name |
string |
Optional | Customer name (defaults to “Walk-in Customer”). |
tax |
integer |
Optional | Applied tax amount (0 if not applicable). |
member_discount |
integer |
Optional | Discount amount for members or loyalty programs. |
total_transaction |
integer |
Yes | Grand total for the purchase, before payment. |
paid |
integer |
Optional | Amount received from customer (used to calculate change). |
paid_return |
integer |
Optional | Change amount returned to the customer. |
payment |
string |
Optional | Payment type (e.g., "Cash", "Credit", "Debit"). Used to decide whether to print credit-related details. |
deposit |
integer |
Optional | Deposit amount (used in partial or credit transactions). |
due_date |
string (YYYY-MM-DD) |
Optional | Due date for credit-based transactions. |
Cart Items (receipt_data.cart[])
| Key | Type | Required | Description |
|---|---|---|---|
item_name |
string |
Yes | Product name or item description. Automatically wrapped for long text. |
price |
integer |
Yes | Unit price of the item. |
qty |
integer |
Yes | Quantity purchased. |
sub_total |
integer |
Optional | Total price for this item (defaults to price * qty if missing). |
Promo Messages (receipt_data.promo[])
| Type | Required | Description |
|---|---|---|
array[string] |
Optional | Each element represents a promotional line or message printed between the item list and the footer. Example: "Buy 2 get 1 free" or "Member discount applied". |
Example: Full Payload
{
"from": "testprinter",
"printer_name": "IW-8001",
"printer_settings": {
"printer_name": "IW-8001",
"interface": "ethernet",
"printer_host": "localhost",
"printer_port": 1100,
"template": "epson",
"pull_cash_drawer": true,
"line_feed_each_in_items": 1,
"more_new_line": 0,
"custom_print_header": [
"DARKTERMINAL MART",
"Jl. Merdeka No. 45, Tegal",
"Open 07:30 - 16:30"
],
"custom_print_footer": [
"Darkterminal Mart",
"Belanja Nyaman, Harga Aman"
],
"custom_language": {
"operator": "Kasir",
"time": "Waktu",
"transaction_number": "No TRX",
"customer_name": "Nama Pelanggan",
"tax": "Pajak",
"member": "Diskon Member",
"total": "Total Belanja",
"paid": "Tunai",
"return": "Kembalian",
"due_date": "Jatuh Tempo",
"saving": "Tabungan",
"loan": "Piutang"
}
},
"receipt_data": {
"app_name": "XYZ MART",
"store_address": "Jl. Universe Metaserve",
"full_name": "Wagiman Artomoro",
"transaction": {
"date_transaction": "2025-10-28",
"transaction": "TRX20251028001",
"customer_name": "Dewi Lestari",
"tax": 3000,
"member_discount": 2000,
"total_transaction": 90000,
"paid": 100000,
"paid_return": 10000,
"payment": "Cash",
"deposit": 0,
"due_date": null
},
"cart": [
{
"item_name": "Kopi Robusta 250gr",
"price": 45000,
"qty": 2,
"sub_total": 90000
},
{
"item_name": "Gula Aren Premium 500gr",
"price": 38000,
"qty": 1,
"sub_total": 38000
}
],
"promo": [
"Beli 2 gratis 1 untuk produk tertentu",
"Diskon member 10% untuk semua kopi lokal"
]
}
}Example: Minimal Payload
{
"from": "posclient",
"printer_name": "IW-8001",
"printer_settings": {
"interface": "ethernet",
"printer_host": "192.168.1.150",
"printer_port": 9100,
"template": "epson"
},
"receipt_data": {
"app_name": "XYZ MART",
"full_name": "Kasir A",
"transaction": {
"date_transaction": "2025-10-28",
"transaction": "TRX20251028001",
"total_transaction": 90000
},
"cart": [
{
"item_name": "Kopi Robusta 250gr",
"price": 45000,
"qty": 2,
"sub_total": 90000
}
]
}
}const ws = new WebSocket("ws://localhost:1945");
ws.onopen = () => ws.send(JSON.stringify(escposPayload));Read CONTRIBUTING.md to contribte this project.
