VYROX PRINTER CONNECTOR - DEVELOPER INTEGRATION GUIDE
Version: 2.0
Updated: 2026-02-04
Website: https://vyrox.com
================================================================================
TABLE OF CONTENTS
================================================================================
1. Overview
2. Architecture
3. Prerequisites
4. Quick Start (Minimum Viable Print)
5. HTML Print Content Structure
6. Method A: Auto-Detection (HTML Element)
7. Method B: JavaScript postMessage API
8. Method C: Popup Trigger (Manual Print)
9. Formatting Tags Reference
9.1 Text Formatting
9.2 Alignment
9.3 Character Size
9.4 Spacing
9.5 Structure and Cutting
9.6 Separators
9.7 Barcodes
9.8 QR Codes
9.9 Images
9.10 Tables and Columns
9.11 Code Pages and Character Sets
9.12 Hardware Control
9.13 Margins and Print Area
9.14 Raw Bytes
9.15 Reset
10. HTML-to-Text Auto-Conversion Rules
11. Paper Size Reference
12. Full Print Examples
12.1 Simple Retail POS
12.2 Restaurant Kitchen Order
12.3 Retail with Barcode and QR
12.4 Multi-Language Print
12.5 Minimal JavaScript-Only Print
12.6 Sports Facility Booking Confirmation
12.7 Snooker and Billiard Table Billing
12.8 Sports Booking with Membership Discount
12.9 Snooker Tournament Scorecard
12.10 Sports Complex Multi-Activity Booking
12.11 Snooker Table Session Timer Slip
13. CSS Classes Auto-Detection
14. Existing Print Button Interception
15. Error Handling
16. Debugging and Troubleshooting
17. Security Considerations
18. Browser Compatibility
19. Advanced Integration Patterns
19.1 Auto-Print After AJAX Order Submission
19.2 Print Queue with Multiple Jobs
19.3 Dual-Copy Printing (Customer + Merchant)
19.4 Conditional Print Based on Printer Status
19.5 Framework Integration (React, Vue, Angular)
20. Frequently Asked Questions
1.
OVERVIEW
VYROX Printer Connector is a Chrome extension that sends print jobs from any
web page to a locally connected ESC/POS thermal printer via USB.
It eliminates the need for:
- Browser print dialogs
- Print preview windows
- CSS print stylesheets
- PDF generation
- Server-side printing software
- Printer-specific SDKs or drivers
The extension detects print content on your web page, converts it to ESC/POS
commands, and sends it directly to the thermal printer through a native host
bridge running on the user's Windows computer.
Who this guide is for:
- Web developers building POS, billing, or order management systems
- PHP, Node.js, Python, or any backend developer serving web pages
- Frontend developers working with React, Vue, Angular, or plain HTML
- Anyone who needs silent thermal printing from a web application
- Sports facility management system developers
- Snooker, billiard, and pool hall billing system developers
- Booking and reservation system developers
2.
ARCHITECTURE
The print flow works as follows:
Your Web Page (HTML/JS)
|
| (1) User clicks print, or your JS sends a message
|
v
Chrome Extension (content.js)
|
| (2) Detects print content element OR receives postMessage
| (3) Converts HTML to tagged plain text (if HTML method)
|
v
Chrome Extension (background.js)
|
| (4) Sends data via Chrome Native Messaging
|
v
Native Host (PowerShell on Windows)
|
| (5) Writes content to print.this file
| (6) Print loop picks up file
| (7) Parses formatting tags into ESC/POS byte commands
|
v
USB Thermal Printer (ESC/POS)
|
| (8) Paper prints and cuts
v
Done
Key points:
- The extension runs on ALL web pages (content script injected globally)
- No server-side component is needed on YOUR server
- The native host runs locally on the user's Windows machine
- Communication is one-way: web page -> printer
- The extension handles all ESC/POS encoding automatically
- Your web page never needs to know about ESC/POS commands
3.
PREREQUISITES
End-user requirements (your customer's computer):
1. Google Chrome browser (version 88 or later)
2. VYROX Printer Connector extension installed from Chrome Web Store
or loaded as unpacked extension
3. VYROX Printer Connector native host installed and running
(VYROX_PrinterConnector.exe, runs on Windows)
4. USB thermal printer connected and detected by the native host
5. Windows operating system (the native host is Windows-only)
Developer requirements (your side):
1. A web page served over HTTP or HTTPS
2. Basic HTML knowledge
3. Optionally: JavaScript for programmatic printing
You do NOT need:
- Any special SDK or library
- Any npm package
- Any server-side print software
- Any modification to your backend
- Any WebSocket or API server
4.
QUICK START (MINIMUM VIABLE PRINT)
The absolute minimum to get printing working is ONE HTML element with a
recognized ID or class. Here is the simplest possible example:
<div id="vyrox_printer_connector">
My Store Name
123 Main Street
--------------------------------
Item A 5.00
Item B 3.50
TOTAL 8.50
Thank you!
</div>
That is it. When the extension detects an element with
id="vyrox_printer_connector" on the page, it automatically shows a floating
print button in the bottom-right corner. The user clicks it, and the content
prints on the thermal printer.
If you want to trigger printing from your own button without the user
clicking the floating button:
<div id="vyrox_printer_connector">
(your print content here)
</div>
<button onclick="window.postMessage({
type: 'VYROX_PRINTER_CONNECTOR_PRINT',
content: document.getElementById('vyrox_printer_connector').innerText
}, '*')">
Print Now
</button>
If you do not have any HTML element on the page and want to print purely
from JavaScript:
window.postMessage({
type: 'VYROX_PRINTER_CONNECTOR_PRINT',
content: '##CENTER####BOLD##Hello World\n##SEP##\nTest print\n##CUT##'
}, '*');
That covers the core usage. Read on for full details and examples.
5.
HTML PRINT CONTENT STRUCTURE
The extension looks for print content using CSS selectors, checked in this
priority order:
1. #vyrox_printer_connector
2. #vyrox-printer-connector
3. #vyrox_printer_connector-content
4. #print-area
5. .vyrox_printer_connector
6. .vyrox-printer-connector
7. .vyrox_printer_connector-content
8. .print-area
9. [data-vyrox-printer-connector="true"]
The FIRST matching element found is used as the print content source.
Recommended approach: use id="vyrox_printer_connector" on your container.
<div id="vyrox_printer_connector">
... your print content ...
</div>
Alternative approaches:
Using the hyphenated ID:
<div id="vyrox-printer-connector">...</div>
Using a class:
<div class="vyrox_printer_connector">...</div>
Using a data attribute:
<div data-vyrox-printer-connector="true">...</div>
Using the print-area ID:
<div id="print-area">...</div>
IMPORTANT: Only ONE print content element should exist on the page at a
time. If multiple matching elements exist, the first one found (in selector
priority order) will be used.
The print content element can be:
- Visible on the page (normal display)
- Hidden with CSS (display:none or visibility:hidden)
- Dynamically created via JavaScript after page load
- Inside a modal, tab, or any container
The extension watches for dynamically added elements using a
MutationObserver, so even if your print content is created after page load
(for example after an AJAX call or a single-page app route change), it
will be detected automatically.
6.
METHOD A: AUTO-DETECTION (HTML ELEMENT)
This is the simplest method. You write your print content as HTML, and the
extension handles everything else.
How it works:
1. The extension scans the page for a matching print content element
2. If found, a floating print button appears (bottom-right corner)
3. When clicked, the extension reads the HTML content
4. It converts the HTML structure into formatted plain text
5. Formatting tags (##CENTER##, ##BOLD##, etc.) are added automatically
based on CSS styles and HTML structure
6. The tagged text is sent to the native host for printing
Example print content in HTML:
<div id="vyrox_printer_connector">
<div style="text-align: center;">
<h2>ACME STORE</h2>
<p>123 Main Street, Suite 100</p>
<p>Tel: 03-1234 5678</p>
</div>
<hr>
<table style="width: 100%;">
<tr>
<td>Americano</td>
<td style="text-align: right;">12.00</td>
</tr>
<tr>
<td>Croissant</td>
<td style="text-align: right;">8.50</td>
</tr>
</table>
<hr>
<table style="width: 100%;">
<tr>
<td><strong>TOTAL</strong></td>
<td style="text-align: right;"><strong>RM 20.50</strong></td>
</tr>
</table>
<hr>
<div style="text-align: center;">
<p>Thank you for your purchase!</p>
<p style="font-size: 10px; color: #999;">Ref #TXN-20260204-001</p>
</div>
</div>
The extension auto-converts this to:
##CENTER####BOLD####BIG##ACME STORE
##CENTER##123 Main Street, Suite 100
##CENTER##Tel: 03-1234 5678
--------------------------------
Americano 12.00
Croissant 8.50
##BOLD##TOTAL RM 20.50
##CENTER##Thank you for your purchase!
##CENTER##Ref #TXN-20260204-001
##FEED3##
##CUT##
You do not need to write any of these tags yourself when using HTML mode.
The extension reads your CSS styles and HTML tags to determine formatting.
7.
METHOD B: JAVASCRIPT postMessage API
For full control over what gets printed, you can send pre-formatted text
directly to the extension using the browser's postMessage API.
This method is ideal when:
- You want exact control over formatting
- Your print content is generated dynamically
- You are building a single-page application (SPA)
- You want to use formatting tags directly
- You need barcodes, QR codes, images, or advanced features
- You do not want any visible print content on the page
Basic usage:
window.postMessage({
type: 'VYROX_PRINTER_CONNECTOR_PRINT',
content: 'Hello World\n##CUT##'
}, '*');
The message object must have:
- type: exactly 'VYROX_PRINTER_CONNECTOR_PRINT' (string, case-sensitive)
- content: the text to print (string, with \n for newlines)
Full example with formatting:
function printBill(orderData) {
var lines = [];
lines.push('##CENTER####BOLD####BIG##ACME STORE');
lines.push('##CENTER##123 Main Street');
lines.push('##CENTER##Tel: 03-1234 5678');
lines.push('##SEP##');
lines.push('');
lines.push('##CENTER####BOLD##SALES BILL');
lines.push('##SEP##');
lines.push('');
lines.push('Date: ' + new Date().toLocaleString());
lines.push('Bill No: ' + orderData.billNumber);
lines.push('');
lines.push('##DSEP##');
lines.push('##TABLE:<Item|^Qty|>Amount##');
lines.push('##SEP##');
orderData.items.forEach(function(item) {
lines.push('##TABLE:<' + item.name
+ '|^' + item.qty
+ '|>' + item.amount.toFixed(2) + '##');
});
lines.push('##DSEP##');
lines.push('');
lines.push('##TABLE:<Subtotal|>' + orderData.subtotal.toFixed(2) + '##');
lines.push('##TABLE:<Tax (6%)|>' + orderData.tax.toFixed(2) + '##');
lines.push('##SEP##');
lines.push('##BOLD####BIG####TABLE:<TOTAL|>RM '
+ orderData.total.toFixed(2) + '##');
lines.push('##SEP##');
lines.push('');
lines.push('##TABLE:<Payment|>' + orderData.paymentMethod + '##');
lines.push('##TABLE:<Tendered|>' + orderData.tendered.toFixed(2) + '##');
lines.push('##TABLE:<Change|>' + orderData.change.toFixed(2) + '##');
lines.push('');
lines.push('##SEP##');
lines.push('##CENTER##Scan for e-bill:');
lines.push('##QR:https://acme.com/bill/'
+ orderData.billNumber + '##');
lines.push('');
lines.push('##CENTER##Thank you!');
lines.push('##CENTER####SMALL##Goods sold are not refundable');
lines.push('');
lines.push('##FEED:4##');
lines.push('##CUT##');
window.postMessage({
type: 'VYROX_PRINTER_CONNECTOR_PRINT',
content: lines.join('\n')
}, '*');
}
// Usage:
printBill({
billNumber: 'TXN-20260204-001',
items: [
{ name: 'Americano', qty: 2, amount: 24.00 },
{ name: 'Croissant', qty: 1, amount: 8.50 },
{ name: 'Muffin', qty: 3, amount: 19.50 }
],
subtotal: 52.00,
tax: 3.12,
total: 55.12,
paymentMethod: 'CASH',
tendered: 60.00,
change: 4.88
});
8.
METHOD C: POPUP TRIGGER (MANUAL PRINT)
Users can also print by clicking the extension icon in Chrome's toolbar
and pressing the "Print Current Page" button in the popup.
This works the same as clicking the floating button: it looks for a
print content element on the current page, extracts the content, and
sends it to the printer.
No developer action is needed for this to work, as long as a valid
print content element exists on the page.
9.
FORMATTING TAGS REFERENCE
Formatting tags are special markers placed inside the text content.
They are processed by the native host and converted to ESC/POS commands
before being sent to the printer.
Tags are:
- Case-sensitive (always UPPERCASE)
- Wrapped in double hashes: ##TAGNAME##
- Placed at the start of a line (for line-level tags)
- Inline with text (for inline formatting tags)
- One or more tags can be combined on a single line
Tags fall into two categories:
STANDALONE TAGS: occupy the entire line, no text follows
Example: ##CUT##
Example: ##SEP##
INLINE TAGS: placed before text on the same line
Example: ##CENTER####BOLD##Hello World
Multiple inline tags can be combined:
##CENTER####BOLD####BIG##GRAND TOTAL
9.1
TEXT FORMATTING
##BOLD##
Prints the text on this line in bold (emphasized).
Example:
##BOLD##Total: RM 25.00
Result: Total: RM 25.00 (printed darker/bolder)
##UNDERLINE##
Prints the text on this line with an underline.
Example:
##UNDERLINE##Section Title
Result: Section Title (with underline beneath)
##INVERSE##
Prints white text on a black background (reverse video).
Example:
##INVERSE## PAID IN FULL
Result: white text on black band
##UPSIDEDOWN##
Prints the text upside-down (180 degree rotation).
Example:
##UPSIDEDOWN##Upside down text
##SMALL##
Uses Font B (smaller characters, more characters per line).
On 80mm paper: approximately 64 characters per line instead of 48.
Example:
##SMALL##This is fine print for terms and conditions
9.2
ALIGNMENT
##CENTER##
Center-aligns the text on this line.
Example:
##CENTER##ACME STORE
##RIGHT##
Right-aligns the text on this line.
Example:
##RIGHT##Page 1 of 1
(no tag)
Default alignment is left.
Example:
This text is left-aligned
9.3
CHARACTER SIZE
##BIG##
Double width AND double height. Good for store names and totals.
Characters per line is halved (e.g., 24 on 80mm paper).
Example:
##CENTER####BIG##ACME STORE
##WIDE##
Double width only. Text is wider but same height.
Characters per line is halved.
Example:
##WIDE##Wide text here
##TALL##
Double height only. Text is taller but same width.
Characters per line stays the same.
Example:
##TALL##Tall text here
##SIZE:WxH##
Granular size control. W = width multiplier (1-8), H = height
multiplier (1-8).
Example:
##SIZE:3x2##Large text
(3x width, 2x height)
Example:
##SIZE:1x4##Very tall thin text
(normal width, 4x height)
Note: characters per line = normal_chars / W
So ##SIZE:4x1## on 80mm paper = 48 / 4 = 12 characters per line.
9.4
SPACING
##LINESPACE:N##
Set the line spacing to N dots. Default is approximately 30 dots.
Lower values pack lines closer together.
Example:
##LINESPACE:20##
Tight line
Another tight line
##LINESPACE:DEFAULT##
##LINESPACE:DEFAULT##
Reset line spacing to the printer's default.
##CHARSPACE:N##
Set the right-side character spacing to N dots. Default is 0.
Higher values add space between characters.
Example:
##CHARSPACE:5##
S P A C E D O U T
##CHARSPACE:0##
9.5
STRUCTURE AND CUTTING
##CUT##
Performs a full paper cut with a small feed.
Typically placed at the very end of a print job.
Example:
Thank you!
##FEED:4##
##CUT##
##PARTCUT##
Performs a partial cut (leaves a small uncut point so the paper
stays attached). Useful for kitchen orders in a queue.
Example:
-- Order #45 --
##FEED:3##
##PARTCUT##
##FEED##
Feeds 3 blank lines (default).
Example:
Last line of content
##FEED##
##CUT##
##FEED:N##
Feeds exactly N blank lines.
Example:
##FEED:5##
(feeds 5 lines)
##FEEDMM:N##
Feeds N motion units (approximately 0.125mm per unit).
For precise spacing control.
Example:
##FEEDMM:40##
(feeds about 5mm)
9.6
SEPARATORS
##SEP##
Prints a full-width line of dashes (-------).
Width matches the paper (32 chars for 58mm, 48 for 80mm, 64 for 112mm).
Example:
Item total
##SEP##
Grand total
##DSEP##
Prints a full-width line of equals signs (=======).
Example:
##DSEP##
BILL
##DSEP##
##STARSEP##
Prints a full-width line of asterisks (*******).
Example:
##STARSEP##
SPECIAL NOTICE
##STARSEP##
Manual separator:
You can also type your own separator in the text content:
--------------------------------
But ##SEP## is preferred because it auto-adjusts to the paper width.
9.7
BARCODES
##BARCODE:TYPE:DATA##
Prints a 1D barcode.
Supported barcode types:
UPC-A 12 digits (11 + check digit)
UPC-E 8 digits
EAN13 13 digits (12 + check digit)
EAN8 8 digits
CODE39 Alphanumeric + special chars
ITF Even number of digits
CODABAR Digits + A-D start/stop
CODE93 Full ASCII
CODE128 Full ASCII (most versatile, recommended)
Examples:
##BARCODE:EAN13:4901234567890##
##BARCODE:CODE128:TXN-2026-0001##
##BARCODE:CODE39:HELLO123##
##BARCODE_HEIGHT:N##
Set barcode height in dots. Default is 80. Range: 1-255.
Place this BEFORE the ##BARCODE## tag.
Example:
##BARCODE_HEIGHT:100##
##BARCODE:CODE128:ABC123##
##BARCODE_WIDTH:N##
Set barcode module width (bar thickness). Default is 3. Range: 2-6.
Larger values make the barcode wider and easier to scan.
Place this BEFORE the ##BARCODE## tag.
Example:
##BARCODE_WIDTH:4##
##BARCODE:CODE128:ABC123##
##BARCODE_HRI:POSITION##
Set the Human Readable Interpretation (the text below/above the
barcode). Options: NONE, ABOVE, BELOW, BOTH. Default is NONE.
Place this BEFORE the ##BARCODE## tag.
Example:
##BARCODE_HRI:BELOW##
##BARCODE:CODE128:TXN-2026-0001##
Result: barcode with "TXN-2026-0001" printed below it
Complete barcode example:
##BARCODE_HEIGHT:80##
##BARCODE_WIDTH:3##
##BARCODE_HRI:BELOW##
##BARCODE:CODE128:TXN-20260204-001##
Note: Barcode settings (height, width, HRI) persist across multiple
barcodes in the same print job. Set them once and they apply to all
subsequent barcodes.
9.8
QR CODES
##QR:DATA##
Prints a QR code containing DATA. DATA can be any text, URL, or
other string content. Supports UTF-8.
Examples:
##QR:https://acme.com/bill/TXN-001##
##QR:WIFI:T:WPA;S:MyNetwork;P:password123;;##
##QR:Hello World##
##QR_SIZE:N##
Set the QR code module size (dot size of each black/white square).
Range: 1-16. Default is 6.
Smaller values = smaller QR code.
Larger values = larger, easier-to-scan QR code.
Place this BEFORE the ##QR## tag.
Example:
##QR_SIZE:8##
##QR:https://example.com##
##QR_EC:LEVEL##
Set the QR code error correction level.
Options:
L = Low (7% recovery)
M = Medium (15% recovery, default)
Q = Quartile (25% recovery)
H = High (30% recovery)
Higher levels make the QR code larger but more resilient to damage.
Place this BEFORE the ##QR## tag.
Example:
##QR_EC:H##
##QR:https://example.com##
Complete QR example:
##CENTER##Scan for e-bill:
##QR_SIZE:6##
##QR_EC:M##
##QR:https://acme.com/bill/TXN-20260204-001##
Note: QR settings persist across multiple QR codes in the same
print job.
9.9
IMAGES
##IMAGE:PATH##
Prints a raster image from a local file path on the user's computer.
Supported formats: BMP, PNG, JPG.
The image is automatically:
- Scaled down to fit the paper width (if wider)
- Converted to 1-bit black and white (threshold at 50% brightness)
- Width rounded to a multiple of 8 pixels
Example:
##IMAGE:C:\VYROX_PrinterConnector\logo.bmp##
Notes:
- The path must be accessible on the local Windows machine
- This is useful for store logos
- For best results, use a black-and-white image
- Recommended width: 384px for 58mm, 576px for 80mm, 832px for 112mm
- Large images may print slowly
Tip: Pre-process your logo as a 1-bit BMP at the correct width for
fastest printing and best quality.
9.10
TABLES AND COLUMNS
##TABLE:col1|col2|col3##
Prints a single row of aligned columns.
Columns are separated by the pipe character (|).
Column widths are distributed evenly across the paper width.
Column alignment prefixes:
< Left-align (default)
> Right-align
^ Center-align
Examples:
Two columns (left + right):
##TABLE:<Americano|>12.00##
Result: Americano 12.00
Three columns (left + center + right):
##TABLE:<2x Americano|^RM|>24.00##
Result: 2x Americano RM 24.00
Header row:
##TABLE:^Item|^Qty|^Price##
Result: Item Qty Price
Full table example:
##BOLD####TABLE:^ITEM|^QTY|^AMOUNT##
##SEP##
##TABLE:<Americano|^2|>24.00##
##TABLE:<Croissant|^1|>8.50##
##TABLE:<Muffin|^3|>19.50##
##SEP##
##BOLD####TABLE:<TOTAL||>RM 52.00##
Note: If text in a column is longer than the column width, it will be
truncated. Plan your content to fit within the available width.
Characters per column = total_chars / number_of_columns
For 80mm paper with 3 columns: 48 / 3 = 16 characters each.
For 80mm paper with 2 columns: 48 / 2 = 24 characters each.
9.11
CODE PAGES AND CHARACTER SETS
##CODEPAGE:N## or ##CODEPAGE:NAME##
Sets the printer's character code table for printing special characters.
This determines which characters are printed for byte values 128-255.
By number:
##CODEPAGE:0## CP437 (US English, default on most printers)
##CODEPAGE:2## CP850 (Western European)
##CODEPAGE:16## CP1252 (Windows Latin-1)
##CODEPAGE:19## CP858 (Western European with Euro sign)
##CODEPAGE:255## UTF-8 (if your printer supports it)
By name:
##CODEPAGE:CP437##
##CODEPAGE:CP850##
##CODEPAGE:CP858##
##CODEPAGE:CP1252##
##CODEPAGE:LATIN1## (same as CP1252)
##CODEPAGE:LATIN2## (same as CP852)
##CODEPAGE:KATAKANA## (Japanese half-width Katakana)
##CODEPAGE:THAI## (Thai, CP874)
##CODEPAGE:CP866## (Cyrillic)
##CODEPAGE:UTF8## (UTF-8, code page 255)
Additional supported code pages:
CP720, CP737, CP775, CP852, CP855, CP857, CP860, CP862, CP863,
CP864, CP865, CP869, CP874, CP1251, CP1253, CP1254, CP1255,
CP1256, CP1257, CP1258
Example for Malaysian Ringgit sign and accented characters:
##CODEPAGE:CP1252##
Total: RM 25.00
Example for Chinese/Unicode (requires printer UTF-8 support):
##CODEPAGE:UTF8##
Chinese text here
Note: Code page support varies by printer model. Most modern thermal
printers support at least CP437, CP850, CP1252, and a few others.
Check your printer's manual for the full list.
9.12
HARDWARE CONTROL
##DRAWER##
Kicks open the cash drawer connected to the printer (pin 2, default).
Example:
##DRAWER##
##CENTER####BIG##ACME STORE
(rest of content...)
##DRAWER:1##
Kicks cash drawer on pin 2 (same as ##DRAWER##).
##DRAWER:2##
Kicks cash drawer on pin 5 (for printers with two drawer ports).
##BEEP:N##
Sounds the printer's built-in buzzer N times (1-9).
Not all printers have a buzzer.
Example:
##BEEP:3##
(beeps 3 times)
##BEEP:N:D##
Sounds the buzzer N times with duration D (1-9).
Higher D values = longer beep duration.
Example:
##BEEP:2:5##
(beeps 2 times, longer duration)
9.13
MARGINS AND PRINT AREA
##MARGIN:N##
Set the left margin to N dots.
Example:
##MARGIN:20##
This text has a left margin
##PRINTAREA:N##
Set the print area width to N dots.
Example:
##PRINTAREA:400##
This text is confined to 400 dots width
Note: These are advanced features. Most print jobs do not need them.
Refer to the Paper Size Reference (section 11) for dots per line.
9.14
RAW BYTES
##RAW:HEXSTRING##
Sends raw hex bytes directly to the printer with no processing.
The hex string must have an even number of characters (0-9, A-F).
This is for advanced users who need to send printer-specific commands
that are not covered by the formatting tags.
Example:
##RAW:1B40##
(sends ESC @ which is the printer initialize command)
Example:
##RAW:1B6101##
(sends ESC a 1 which is center alignment)
Common ESC/POS hex commands:
1B 40 ESC @ Initialize printer
1B 61 00 ESC a 0 Left align
1B 61 01 ESC a 1 Center align
1B 61 02 ESC a 2 Right align
1B 45 01 ESC E 1 Bold on
1B 45 00 ESC E 0 Bold off
1D 56 42 03 GS V B 3 Cut paper with feed
9.15
RESET
##RESET##
Sends the ESC @ command to reinitialize the printer to its default
state. This clears all formatting, alignment, and size settings.
Useful in the middle of a print job if formatting gets confused:
##BOLD####BIG##HEADER
##RESET##
Normal text after reset
10.
HTML-TO-TEXT AUTO-CONVERSION RULES
When using Method A (HTML element), the extension automatically converts
your HTML into tagged text. Here is how it maps HTML/CSS to formatting:
ALIGNMENT DETECTION:
text-align: center -> ##CENTER##
class="center" -> ##CENTER##
class="text-center" -> ##CENTER##
BOLD DETECTION:
<strong> or <b> tag -> ##BOLD##
font-weight >= 700 -> ##BOLD##
class="bold" -> ##BOLD##
class="font-bold" -> ##BOLD##
class="fw-bold" -> ##BOLD##
LARGE TEXT DETECTION:
font-size >= 20px -> ##BIG##
<h1> or <h2> tag -> ##BIG##
class="big" -> ##BIG##
class="large" -> ##BIG##
class="title" -> ##BIG##
class="total" -> ##BIG##
class="text-xl" -> ##BIG##
class="text-2xl" -> ##BIG##
SEPARATOR DETECTION:
<hr> tag -> --------------------------------
class="separator" -> --------------------------------
class="divider" -> --------------------------------
class="line" -> --------------------------------
(only if element has no text or only dashes/equals/dots)
CUT DETECTION:
class="cut" -> ##CUT##
data-cut="true" -> ##CUT##
FEED DETECTION:
class="feed" -> ##FEED3##
data-feed="5" -> ##FEED5##
TABLE DETECTION:
<table> with <tr>/<td> elements are auto-formatted into aligned
columns.
2-column tables: left-aligned first column, right-aligned second
column (ideal for item + price layout).
3-column tables: quantity (padded to 4 chars), item name, price
(right-aligned).
Other column counts: joined with double-space.
AUTO FEED AND CUT:
If your print content does not end with ##CUT##, the extension
automatically appends:
##FEED3##
##CUT##
This ensures the paper feeds enough for tearing and the cutter
activates.
PRE-FORMATTED TEXT:
If your element has a data-vyrox-printer-connector-raw attribute,
its value is used as-is (no HTML parsing):
<div id="vyrox_printer_connector"
data-vyrox-printer-connector-raw="##CENTER##My Store\nItem 1 5.00\n##CUT##">
</div>
If your element contains a child element with
class="vyrox-printer-connector-raw" or a
<pre class="print-content-text"> element, its text content is used
as-is:
<div id="vyrox_printer_connector">
<pre class="print-content-text" style="display:none;">
##CENTER####BOLD####BIG##MY STORE
##CENTER##123 Main Street
##SEP##
Americano 12.00
##SEP##
##BOLD##TOTAL RM 12.00
##FEED:4##
##CUT##
</pre>
<!-- visible HTML version for screen display -->
<h2>MY STORE</h2>
...
</div>
This is the recommended approach when you need precise control over
the print output while also showing a formatted version on screen.
11.
PAPER SIZE REFERENCE
58mm paper (2-inch, small/portable printers):
Characters per line (Font A): 32
Characters per line (Font B): 42
Dots per line: 384
Typical use: Mobile billing, parking tickets, small kiosks
80mm paper (3-inch, standard POS printers):
Characters per line (Font A): 48
Characters per line (Font B): 64
Dots per line: 576
Typical use: Retail POS, restaurant orders, most billing systems
112mm paper (4-inch, wide/kitchen printers):
Characters per line (Font A): 64
Characters per line (Font B): 85
Dots per line: 832
Typical use: Kitchen orders, detailed bills, wide-format printouts
The paper size is auto-detected by the native host during setup.
All ##SEP##, ##DSEP##, ##STARSEP##, and ##TABLE## tags auto-adjust
to the detected paper width. You do NOT need to hard-code character
counts.
Character count under different size modes (80mm paper example):
Normal text: 48 characters
##SMALL## (Font B): 64 characters
##BIG## or ##WIDE##: 24 characters
##SIZE:3x1##: 16 characters
##SIZE:4x1##: 12 characters
12.
FULL PRINT EXAMPLES
12.1
SIMPLE RETAIL POS
This is the most common billing format. Use this as your starting template.
Using postMessage:
var bill = [
'##CENTER####BOLD####BIG##ACME CAFE',
'##CENTER##Lot 12, Setia City Mall',
'##CENTER##Tel: 03-1234 5678',
'##DSEP##',
'##CENTER####BOLD##SALES BILL',
'##DSEP##',
'',
'Date: 2026-02-04 14:30',
'Cashier: Ahmad',
'Bill No: #B-20260204-0042',
'',
'##SEP##',
'##TABLE:<Americano x2|>24.00##',
'##TABLE:<Latte x1|>14.00##',
'##TABLE:<Croissant x1|>8.50##',
'##TABLE:<Cheesecake x1|>16.00##',
'##SEP##',
'##TABLE:<Subtotal|>62.50##',
'##TABLE:<Service Tax 6%|>3.75##',
'##SEP##',
'##BOLD####BIG####TABLE:<TOTAL|>RM 66.25##',
'##SEP##',
'',
'##TABLE:<Cash|>70.00##',
'##TABLE:<Change|>3.75##',
'',
'##SEP##',
'##CENTER##Thank you for dining with us!',
'##CENTER####SMALL##GST Reg: 001234567890',
'##CENTER####SMALL##SST-02-1234-56789012',
'',
'##FEED:4##',
'##CUT##'
].join('\n');
window.postMessage({
type: 'VYROX_PRINTER_CONNECTOR_PRINT',
content: bill
}, '*');
Using HTML auto-detection:
<div id="vyrox_printer_connector">
<div style="text-align:center;">
<h2>ACME CAFE</h2>
<p>Lot 12, Setia City Mall</p>
<p>Tel: 03-1234 5678</p>
</div>
<hr>
<p style="text-align:center;"><strong>SALES BILL</strong></p>
<hr>
<p>Date: 2026-02-04 14:30</p>
<p>Cashier: Ahmad</p>
<p>Bill No: #B-20260204-0042</p>
<hr>
<table style="width:100%">
<tr><td>Americano x2</td><td style="text-align:right">24.00</td></tr>
<tr><td>Latte x1</td><td style="text-align:right">14.00</td></tr>
<tr><td>Croissant x1</td><td style="text-align:right">8.50</td></tr>
<tr><td>Cheesecake x1</td><td style="text-align:right">16.00</td></tr>
</table>
<hr>
<table style="width:100%">
<tr><td>Subtotal</td><td style="text-align:right">62.50</td></tr>
<tr><td>Service Tax 6%</td><td style="text-align:right">3.75</td></tr>
</table>
<hr>
<table style="width:100%">
<tr>
<td><strong style="font-size:20px">TOTAL</strong></td>
<td style="text-align:right;font-size:20px">
<strong>RM 66.25</strong>
</td>
</tr>
</table>
<hr>
<table style="width:100%">
<tr><td>Cash</td><td style="text-align:right">70.00</td></tr>
<tr><td>Change</td><td style="text-align:right">3.75</td></tr>
</table>
<hr>
<p style="text-align:center">Thank you for dining with us!</p>
</div>
12.2
RESTAURANT KITCHEN ORDER
var kitchenOrder = [
'##BEEP:3##',
'##CENTER####BOLD####BIG##KITCHEN ORDER',
'##DSEP##',
'',
'##BOLD##Table: 12',
'##BOLD##Time: 14:32',
'##BOLD##Server: Siti',
'',
'##DSEP##',
'##BOLD####BIG##2x Nasi Lemak Special',
' - Extra sambal',
' - No cucumber',
'',
'##BOLD####BIG##1x Mee Goreng Mamak',
' - Extra spicy',
'',
'##BOLD####BIG##3x Teh Tarik',
'',
'##BOLD####BIG##1x Iced Milo Dinosaur',
'',
'##DSEP##',
'##CENTER####BOLD##PRIORITY: NORMAL',
'##DSEP##',
'',
'##FEED:3##',
'##PARTCUT##'
].join('\n');
window.postMessage({
type: 'VYROX_PRINTER_CONNECTOR_PRINT',
content: kitchenOrder
}, '*');
12.3
RETAIL WITH BARCODE AND QR
var retailBill = [
'##CENTER####BOLD####BIG##GADGET WORLD',
'##CENTER##Plaza 123, Level 2',
'##CENTER##Tel: 011-2233 4455',
'##DSEP##',
'',
'Date: 2026-02-04 15:45',
'Bill No: GW-20260204-0108',
'Staff: Raj',
'',
'##SEP##',
'##TABLE:<USB-C Cable 2m|^1|>29.90##',
'##TABLE:<Phone Case X15|^1|>49.90##',
'##TABLE:<Screen Protector|^2|>19.80##',
'##SEP##',
'##TABLE:<Subtotal|>99.60##',
'##TABLE:<Member Disc 10%|>-9.96##',
'##SEP##',
'##BOLD####BIG####CENTER##RM 89.64',
'##SEP##',
'',
'##TABLE:<E-Wallet|>89.64##',
'##TABLE:<Ref: TNG-8834521|>##',
'',
'##SEP##',
'',
'##CENTER##Scan to check warranty:',
'##QR_SIZE:8##',
'##QR:https://gadgetworld.com/warranty/GW-20260204-0108##',
'',
'##CENTER##Bill barcode:',
'##BARCODE_HRI:BELOW##',
'##BARCODE:CODE128:GW202602040108##',
'',
'##SEP##',
'##CENTER####SMALL##Exchange within 7 days with bill',
'##CENTER####SMALL##No refund on accessories',
'',
'##FEED:4##',
'##CUT##'
].join('\n');
window.postMessage({
type: 'VYROX_PRINTER_CONNECTOR_PRINT',
content: retailBill
}, '*');
12.4
MULTI-LANGUAGE PRINT
var multiLangBill = [
'##CODEPAGE:UTF8##',
'##CENTER####BOLD####BIG##RESTORAN ABC',
'##CENTER##No. 5, Jalan Maju',
'##DSEP##',
'',
'##TABLE:<Nasi Kandar|>15.00##',
'##TABLE:<Roti Canai|>2.50##',
'##TABLE:<Teh Tarik|>3.00##',
'##SEP##',
'##BOLD####TABLE:<Jumlah / Total|>RM 20.50##',
'##SEP##',
'',
'##CENTER##Terima kasih!',
'##CENTER##Thank you!',
'',
'##FEED:4##',
'##CUT##'
].join('\n');
window.postMessage({
type: 'VYROX_PRINTER_CONNECTOR_PRINT',
content: multiLangBill
}, '*');
12.5
MINIMAL JAVASCRIPT-ONLY PRINT
If you do not have any print content HTML on the page and just want to
print from a JavaScript event (e.g., after an AJAX order submission):
fetch('/api/orders', { method: 'POST', body: orderData })
.then(function(res) { return res.json(); })
.then(function(order) {
var lines = [];
lines.push('##CENTER####BOLD##Order #' + order.id);
lines.push('##SEP##');
order.items.forEach(function(item) {
lines.push('##TABLE:<' + item.name
+ '|^' + item.qty
+ '|>' + item.price.toFixed(2) + '##');
});
lines.push('##SEP##');
lines.push('##BOLD####TABLE:<TOTAL|>RM '
+ order.total.toFixed(2) + '##');
lines.push('##FEED:4##');
lines.push('##CUT##');
window.postMessage({
type: 'VYROX_PRINTER_CONNECTOR_PRINT',
content: lines.join('\n')
}, '*');
});
No HTML print content element is needed. No floating button will appear.
The print is entirely triggered by JavaScript.
12.6
SPORTS FACILITY BOOKING CONFIRMATION
This example shows a booking confirmation for a badminton court, futsal
pitch, or any sports facility. Includes time slot, booking reference,
and QR code for check-in.
function printBookingConfirmation(booking) {
var lines = [];
lines.push('##CENTER####BOLD####BIG##SPORTSPARK');
lines.push('##CENTER##Shah Alam Sports Complex');
lines.push('##CENTER##Jalan Kelab, 40000 Shah Alam');
lines.push('##CENTER##Tel: 03-5511 2233');
lines.push('##DSEP##');
lines.push('##CENTER####BOLD##BOOKING CONFIRMATION');
lines.push('##DSEP##');
lines.push('');
lines.push('Booking Ref: ' + booking.ref);
lines.push('Booked By: ' + booking.customerName);
lines.push('Contact: ' + booking.phone);
lines.push('Printed: ' + new Date().toLocaleString());
lines.push('');
lines.push('##SEP##');
lines.push('##BOLD##Facility Details');
lines.push('##SEP##');
lines.push('');
lines.push('##TABLE:<Facility|>' + booking.facilityName + '##');
lines.push('##TABLE:<Court/Pitch|>' + booking.courtName + '##');
lines.push('##TABLE:<Date|>' + booking.date + '##');
lines.push('##TABLE:<Time|>'
+ booking.startTime + ' - ' + booking.endTime + '##');
lines.push('##TABLE:<Duration|>'
+ booking.durationHours + ' hour(s)##');
lines.push('');
lines.push('##SEP##');
lines.push('##BOLD##Payment Summary');
lines.push('##SEP##');
lines.push('');
lines.push('##TABLE:<Rate per hour|>RM '
+ booking.ratePerHour.toFixed(2) + '##');
lines.push('##TABLE:<Hours|>'
+ booking.durationHours + '##');
if (booking.discount > 0) {
lines.push('##TABLE:<Member Discount|>-RM '
+ booking.discount.toFixed(2) + '##');
}
lines.push('##SEP##');
lines.push('##BOLD####BIG####TABLE:<TOTAL|>RM '
+ booking.total.toFixed(2) + '##');
lines.push('##SEP##');
lines.push('');
lines.push('##TABLE:<Payment|>' + booking.paymentMethod + '##');
if (booking.paymentRef) {
lines.push('##TABLE:<Ref|>' + booking.paymentRef + '##');
}
lines.push('');
lines.push('##DSEP##');
lines.push('');
lines.push('##CENTER##Show this QR at the counter:');
lines.push('##QR_SIZE:8##');
lines.push('##QR_EC:H##');
lines.push('##QR:https://sportspark.com/checkin/'
+ booking.ref + '##');
lines.push('');
lines.push('##BARCODE_HRI:BELOW##');
lines.push('##BARCODE:CODE128:' + booking.ref + '##');
lines.push('');
lines.push('##SEP##');
lines.push('##CENTER####SMALL##Please arrive 15 min before');
lines.push('##CENTER####SMALL##your booking time.');
lines.push('##CENTER####SMALL##No-show bookings are non-refundable.');
lines.push('##CENTER####SMALL##Cancellation: 24 hours notice required.');
lines.push('');
lines.push('##CENTER##Thank you for choosing SportsPark!');
lines.push('');
lines.push('##FEED:4##');
lines.push('##CUT##');
window.postMessage({
type: 'VYROX_PRINTER_CONNECTOR_PRINT',
content: lines.join('\n')
}, '*');
}
// Usage:
printBookingConfirmation({
ref: 'BK-20260204-0089',
customerName: 'Tan Wei Ming',
phone: '012-345 6789',
facilityName: 'Badminton',
courtName: 'Court A3',
date: '2026-02-05',
startTime: '19:00',
endTime: '21:00',
durationHours: 2,
ratePerHour: 25.00,
discount: 5.00,
total: 45.00,
paymentMethod: 'Online (FPX)',
paymentRef: 'FPX-88712340'
});
12.7
SNOOKER AND BILLIARD TABLE BILLING
This example shows a complete snooker or billiard hall session bill with
time-based charging, food and beverage items, and table details.
function printSnookerBill(session) {
var lines = [];
lines.push('##DRAWER##');
lines.push('##CENTER####BOLD####BIG##Q-BALL SNOOKER');
lines.push('##CENTER##Level 3, Plaza Pelangi');
lines.push('##CENTER##80400 Johor Bahru');
lines.push('##CENTER##Tel: 07-3344 5566');
lines.push('##DSEP##');
lines.push('##CENTER####BOLD##TABLE BILL');
lines.push('##DSEP##');
lines.push('');
lines.push('Bill No: ' + session.billNo);
lines.push('Date: ' + session.date);
lines.push('Cashier: ' + session.cashier);
lines.push('');
lines.push('##SEP##');
lines.push('##BOLD##Table Session');
lines.push('##SEP##');
lines.push('');
lines.push('##TABLE:<Table|>'
+ session.tableName + '##');
lines.push('##TABLE:<Type|>'
+ session.tableType + '##');
lines.push('##TABLE:<Start|>'
+ session.startTime + '##');
lines.push('##TABLE:<End|>'
+ session.endTime + '##');
lines.push('##TABLE:<Duration|>'
+ session.durationDisplay + '##');
lines.push('##TABLE:<Rate|>RM '
+ session.ratePerHour.toFixed(2) + '/hr##');
lines.push('');
lines.push('##SEP##');
lines.push('##BOLD####TABLE:<Table Charge|>RM '
+ session.tableCharge.toFixed(2) + '##');
lines.push('##SEP##');
lines.push('');
if (session.fnbItems && session.fnbItems.length > 0) {
lines.push('##BOLD##Food and Beverages');
lines.push('##SEP##');
lines.push('');
session.fnbItems.forEach(function(item) {
lines.push('##TABLE:<' + item.name
+ '|^' + item.qty
+ '|>RM ' + item.amount.toFixed(2) + '##');
});
lines.push('##SEP##');
lines.push('##BOLD####TABLE:<F&B Subtotal|>RM '
+ session.fnbTotal.toFixed(2) + '##');
lines.push('##SEP##');
lines.push('');
}
lines.push('##DSEP##');
lines.push('##TABLE:<Table Charge|>RM '
+ session.tableCharge.toFixed(2) + '##');
if (session.fnbItems && session.fnbItems.length > 0) {
lines.push('##TABLE:<F&B|>RM '
+ session.fnbTotal.toFixed(2) + '##');
}
if (session.serviceTax > 0) {
lines.push('##TABLE:<Service Tax 6%|>RM '
+ session.serviceTax.toFixed(2) + '##');
}
if (session.memberDiscount > 0) {
lines.push('##TABLE:<Member Disc|>-RM '
+ session.memberDiscount.toFixed(2) + '##');
}
lines.push('##DSEP##');
lines.push('##BOLD####BIG####CENTER##RM '
+ session.grandTotal.toFixed(2));
lines.push('##DSEP##');
lines.push('');
lines.push('##TABLE:<Payment|>' + session.paymentMethod + '##');
lines.push('##TABLE:<Tendered|>RM '
+ session.tendered.toFixed(2) + '##');
lines.push('##TABLE:<Change|>RM '
+ session.change.toFixed(2) + '##');
lines.push('');
if (session.memberName) {
lines.push('##SEP##');
lines.push('##TABLE:<Member|>' + session.memberName + '##');
lines.push('##TABLE:<Member ID|>' + session.memberId + '##');
lines.push('##TABLE:<Points Earned|>+'
+ session.pointsEarned + '##');
lines.push('##TABLE:<Points Balance|>'
+ session.pointsBalance + '##');
lines.push('');
}
lines.push('##SEP##');
lines.push('');
lines.push('##CENTER##Scan to join membership:');
lines.push('##QR_SIZE:6##');
lines.push('##QR:https://qball.com/member/join##');
lines.push('');
lines.push('##SEP##');
lines.push('##CENTER####SMALL##Operating hours: 12pm - 2am daily');
lines.push('##CENTER####SMALL##Happy hour rate: 12pm - 5pm');
lines.push('##CENTER####SMALL##Follow us: @qballsnooker');
lines.push('');
lines.push('##CENTER##See you at the table!');
lines.push('');
lines.push('##FEED:4##');
lines.push('##CUT##');
window.postMessage({
type: 'VYROX_PRINTER_CONNECTOR_PRINT',
content: lines.join('\n')
}, '*');
}
// Usage:
printSnookerBill({
billNo: 'QB-20260204-0156',
date: '2026-02-04 23:15',
cashier: 'Kumar',
tableName: 'Table 7',
tableType: 'Snooker (Full Size)',
startTime: '20:30',
endTime: '23:10',
durationDisplay: '2h 40m',
ratePerHour: 18.00,
tableCharge: 48.00,
fnbItems: [
{ name: 'Canned Drink', qty: 3, amount: 10.50 },
{ name: 'Hot Coffee', qty: 2, amount: 7.00 },
{ name: 'Maggi Goreng', qty: 1, amount: 8.00 },
{ name: 'Roti Bakar', qty: 2, amount: 6.00 }
],
fnbTotal: 31.50,
serviceTax: 4.77,
memberDiscount: 4.80,
grandTotal: 79.47,
paymentMethod: 'CASH',
tendered: 80.00,
change: 0.53,
memberName: 'Ali bin Abu',
memberId: 'QBM-0042',
pointsEarned: 79,
pointsBalance: 1245
});
12.8
SPORTS BOOKING WITH MEMBERSHIP DISCOUNT
A futsal booking example showing group booking, membership tiers, and
deposit/balance payment flow.
function printFutsalBooking(booking) {
var lines = [];
lines.push('##CENTER####BOLD####BIG##KICK ARENA');
lines.push('##CENTER##Futsal and Sports Centre');
lines.push('##CENTER##Setia Alam, Shah Alam');
lines.push('##CENTER##Tel: 03-5123 4567');
lines.push('##DSEP##');
lines.push('##CENTER####BOLD##BOOKING BILL');
lines.push('##DSEP##');
lines.push('');
lines.push('Booking ID: ' + booking.id);
lines.push('Date Booked: ' + booking.bookedDate);
lines.push('Booked By: ' + booking.customerName);
lines.push('Phone: ' + booking.phone);
lines.push('');
lines.push('##SEP##');
lines.push('##BOLD##Session Details');
lines.push('##SEP##');
lines.push('');
lines.push('##TABLE:<Activity|>' + booking.activity + '##');
lines.push('##TABLE:<Pitch|>' + booking.pitchName + '##');
lines.push('##TABLE:<Session Date|>' + booking.sessionDate + '##');
lines.push('##TABLE:<Time|>'
+ booking.startTime + ' - ' + booking.endTime + '##');
lines.push('##TABLE:<Duration|>'
+ booking.durationHours + ' hour(s)##');
lines.push('##TABLE:<Players|>'
+ booking.playerCount + ' pax##');
lines.push('');
if (booking.extras && booking.extras.length > 0) {
lines.push('##BOLD##Add-Ons');
lines.push('##SEP##');
lines.push('');
booking.extras.forEach(function(extra) {
lines.push('##TABLE:<' + extra.name
+ '|^' + extra.qty
+ '|>RM ' + extra.amount.toFixed(2) + '##');
});
lines.push('##SEP##');
lines.push('');
}
lines.push('##BOLD##Payment Breakdown');
lines.push('##SEP##');
lines.push('');
lines.push('##TABLE:<Pitch Rate|>RM '
+ booking.ratePerHour.toFixed(2) + '/hr##');
lines.push('##TABLE:<Hours|>'
+ booking.durationHours + '##');
lines.push('##TABLE:<Pitch Charge|>RM '
+ booking.pitchCharge.toFixed(2) + '##');
if (booking.extrasTotal > 0) {
lines.push('##TABLE:<Add-Ons|>RM '
+ booking.extrasTotal.toFixed(2) + '##');
}
if (booking.memberTier) {
lines.push('##TABLE:<Member Tier|>'
+ booking.memberTier + '##');
lines.push('##TABLE:<Discount ('
+ booking.discountPercent + '%)|>-RM '
+ booking.discountAmount.toFixed(2) + '##');
}
lines.push('##SEP##');
lines.push('##BOLD####BIG####TABLE:<TOTAL|>RM '
+ booking.total.toFixed(2) + '##');
lines.push('##SEP##');
lines.push('');
lines.push('##TABLE:<Deposit Paid|>RM '
+ booking.depositPaid.toFixed(2) + '##');
lines.push('##BOLD####TABLE:<Balance Due|>RM '
+ booking.balanceDue.toFixed(2) + '##');
lines.push('##TABLE:<Pay Before|>'
+ booking.payBeforeDate + '##');
lines.push('');
lines.push('##DSEP##');
lines.push('');
lines.push('##CENTER##Show this QR for check-in:');
lines.push('##QR_SIZE:8##');
lines.push('##QR_EC:H##');
lines.push('##QR:https://kickarena.com/checkin/'
+ booking.id + '##');
lines.push('');
lines.push('##SEP##');
lines.push('##CENTER####SMALL##Deposit is non-refundable.');
lines.push('##CENTER####SMALL##Full payment required on session day.');
lines.push('##CENTER####SMALL##Reschedule: min 12 hours notice.');
lines.push('##CENTER####SMALL##Bibs and balls provided at counter.');
lines.push('');
lines.push('##CENTER##See you on the pitch!');
lines.push('');
lines.push('##FEED:4##');
lines.push('##CUT##');
window.postMessage({
type: 'VYROX_PRINTER_CONNECTOR_PRINT',
content: lines.join('\n')
}, '*');
}
// Usage:
printFutsalBooking({
id: 'KA-20260205-0034',
bookedDate: '2026-02-04 10:15',
customerName: 'Mohd Faizal',
phone: '019-876 5432',
activity: 'Futsal',
pitchName: 'Pitch B (Indoor)',
sessionDate: '2026-02-08',
startTime: '20:00',
endTime: '22:00',
durationHours: 2,
playerCount: 12,
ratePerHour: 120.00,
pitchCharge: 240.00,
extras: [
{ name: 'Bibs (12 pcs)', qty: 1, amount: 10.00 },
{ name: 'Drinking Water', qty: 12, amount: 18.00 }
],
extrasTotal: 28.00,
memberTier: 'Gold',
discountPercent: 15,
discountAmount: 36.00,
total: 232.00,
depositPaid: 120.00,
balanceDue: 112.00,
payBeforeDate: '2026-02-08 19:30'
});
12.9
SNOOKER TOURNAMENT SCORECARD
A tournament match scorecard printed at the end of each frame or match,
showing player names, frame scores, and match result.
function printTournamentScorecard(match) {
var lines = [];
lines.push('##CENTER####BOLD####BIG##Q-BALL SNOOKER');
lines.push('##DSEP##');
lines.push('##CENTER####BOLD####BIG##' + match.tournamentName);
lines.push('##CENTER##' + match.round);
lines.push('##DSEP##');
lines.push('');
lines.push('Match ID: ' + match.matchId);
lines.push('Date: ' + match.date);
lines.push('Table: ' + match.tableName);
lines.push('Referee: ' + match.referee);
lines.push('');
lines.push('##DSEP##');
lines.push('##BOLD####TABLE:<PLAYER|^FRAMES|>H/BREAK##');
lines.push('##DSEP##');
lines.push('');
lines.push('##TABLE:<' + match.player1.name
+ '|^' + match.player1.framesWon
+ '|>' + (match.player1.highBreak || '-') + '##');
lines.push('##CENTER##vs');
lines.push('##TABLE:<' + match.player2.name
+ '|^' + match.player2.framesWon
+ '|>' + (match.player2.highBreak || '-') + '##');
lines.push('');
lines.push('##DSEP##');
lines.push('');
lines.push('##BOLD##Frame-by-Frame');
lines.push('##SEP##');
lines.push('##TABLE:<Frame|^'
+ match.player1.name.substring(0, 10) + '|^'
+ match.player2.name.substring(0, 10) + '##');
lines.push('##SEP##');
match.frames.forEach(function(frame, index) {
var frameNum = (index + 1).toString();
var winMarker1 = frame.winner === 1 ? ' *' : '';
var winMarker2 = frame.winner === 2 ? ' *' : '';
lines.push('##TABLE:<' + frameNum
+ '|^' + frame.score1 + winMarker1
+ '|^' + frame.score2 + winMarker2 + '##');
});
lines.push('##DSEP##');
lines.push('');
lines.push('##CENTER####BOLD####BIG##WINNER');
lines.push('##CENTER####BOLD####BIG##' + match.winnerName);
lines.push('');
lines.push('##CENTER##Final Score: '
+ match.player1.framesWon + ' - '
+ match.player2.framesWon);
lines.push('');
if (match.highestBreak) {
lines.push('##CENTER####BOLD##Match Highest Break: '
+ match.highestBreak.score
+ ' (' + match.highestBreak.player + ')');
lines.push('');
}
lines.push('##SEP##');
lines.push('##TABLE:<Match Start|>'
+ match.startTime + '##');
lines.push('##TABLE:<Match End|>'
+ match.endTime + '##');
lines.push('##TABLE:<Total Time|>'
+ match.totalDuration + '##');
lines.push('');
lines.push('##SEP##');
lines.push('##CENTER####SMALL##Official tournament record');
lines.push('##CENTER####SMALL##' + match.tournamentName);
lines.push('##CENTER####SMALL##Sanctioned by '
+ match.sanctionedBy);
lines.push('');
lines.push('##CENTER##Signed: ___________________');
lines.push('##CENTER####SMALL##Referee');
lines.push('');
lines.push('##FEED:4##');
lines.push('##CUT##');
window.postMessage({
type: 'VYROX_PRINTER_CONNECTOR_PRINT',
content: lines.join('\n')
}, '*');
}
// Usage:
printTournamentScorecard({
tournamentName: 'Q-BALL OPEN 2026',
round: 'Quarter Final',
matchId: 'QBO-QF-003',
date: '2026-02-04',
tableName: 'Table 1 (Match Table)',
referee: 'Encik Razak',
player1: {
name: 'James Lee',
framesWon: 4,
highBreak: 87
},
player2: {
name: 'Ahmad Rizal',
framesWon: 2,
highBreak: 63
},
frames: [
{ score1: 72, score2: 45, winner: 1 },
{ score1: 38, score2: 69, winner: 2 },
{ score1: 87, score2: 22, winner: 1 },
{ score1: 51, score2: 63, winner: 2 },
{ score1: 66, score2: 41, winner: 1 },
{ score1: 59, score2: 30, winner: 1 }
],
winnerName: 'James Lee',
highestBreak: { score: 87, player: 'James Lee' },
startTime: '14:00',
endTime: '16:45',
totalDuration: '2h 45m',
sanctionedBy: 'Johor Snooker Association'
});
12.10
SPORTS COMPLEX MULTI-ACTIVITY BOOKING
A sports complex bill that covers multiple activities booked together,
such as swimming plus gym plus court rental in a single transaction.
function printMultiActivityBill(bill) {
var lines = [];
lines.push('##CENTER####BOLD####BIG##ACTIVE SPORTS HUB');
lines.push('##CENTER##Persiaran Setia, Setia Alam');
lines.push('##CENTER##40170 Shah Alam, Selangor');
lines.push('##CENTER##Tel: 03-3345 6677');
lines.push('##DSEP##');
lines.push('##CENTER####BOLD##ACTIVITY BILL');
lines.push('##DSEP##');
lines.push('');
lines.push('Bill No: ' + bill.billNo);
lines.push('Date: ' + bill.date);
lines.push('Staff: ' + bill.staff);
lines.push('Customer: ' + bill.customerName);
if (bill.memberId) {
lines.push('Member ID: ' + bill.memberId);
lines.push('Tier: ' + bill.memberTier);
}
lines.push('');
bill.activities.forEach(function(activity, index) {
if (index > 0) lines.push('');
lines.push('##BOLD##' + activity.name);
lines.push('##SEP##');
if (activity.facility) {
lines.push('##TABLE:<Facility|>'
+ activity.facility + '##');
}
if (activity.timeSlot) {
lines.push('##TABLE:<Time|>'
+ activity.timeSlot + '##');
}
if (activity.duration) {
lines.push('##TABLE:<Duration|>'
+ activity.duration + '##');
}
if (activity.pax) {
lines.push('##TABLE:<Pax|>'
+ activity.pax + '##');
}
if (activity.details && activity.details.length > 0) {
activity.details.forEach(function(detail) {
lines.push('##TABLE:<' + detail.label
+ '|>' + detail.value + '##');
});
}
lines.push('##TABLE:<Charge|>RM '
+ activity.charge.toFixed(2) + '##');
});
lines.push('');
lines.push('##DSEP##');
lines.push('##BOLD##Summary');
lines.push('##DSEP##');
lines.push('');
bill.activities.forEach(function(activity) {
lines.push('##TABLE:<' + activity.name + '|>RM '
+ activity.charge.toFixed(2) + '##');
});
lines.push('##SEP##');
lines.push('##TABLE:<Subtotal|>RM '
+ bill.subtotal.toFixed(2) + '##');
if (bill.discount > 0) {
lines.push('##TABLE:<'
+ bill.discountLabel + '|>-RM '
+ bill.discount.toFixed(2) + '##');
}
if (bill.tax > 0) {
lines.push('##TABLE:<Service Tax 6%|>RM '
+ bill.tax.toFixed(2) + '##');
}
lines.push('##DSEP##');
lines.push('##BOLD####BIG####CENTER##RM '
+ bill.grandTotal.toFixed(2));
lines.push('##DSEP##');
lines.push('');
lines.push('##TABLE:<Payment|>'
+ bill.paymentMethod + '##');
if (bill.paymentRef) {
lines.push('##TABLE:<Ref|>'
+ bill.paymentRef + '##');
}
lines.push('');
if (bill.pointsEarned) {
lines.push('##SEP##');
lines.push('##TABLE:<Points Earned|>+'
+ bill.pointsEarned + '##');
lines.push('##TABLE:<Total Points|>'
+ bill.totalPoints + '##');
lines.push('');
}
lines.push('##SEP##');
lines.push('');
lines.push('##CENTER##Download our app:');
lines.push('##QR_SIZE:6##');
lines.push('##QR:https://activesportshub.com/app##');
lines.push('');
lines.push('##SEP##');
lines.push('##CENTER####SMALL##All bookings are subject to');
lines.push('##CENTER####SMALL##availability and our T&C.');
lines.push('##CENTER####SMALL##www.activesportshub.com/terms');
lines.push('');
lines.push('##CENTER##Stay active, stay healthy!');
lines.push('');
lines.push('##FEED:4##');
lines.push('##CUT##');
window.postMessage({
type: 'VYROX_PRINTER_CONNECTOR_PRINT',
content: lines.join('\n')
}, '*');
}
// Usage:
printMultiActivityBill({
billNo: 'ASH-20260204-0211',
date: '2026-02-04 18:30',
staff: 'Nurul',
customerName: 'Lim Chee Keong',
memberId: 'ASH-G-0088',
memberTier: 'Gold',
activities: [
{
name: 'Badminton Court',
facility: 'Court B2',
timeSlot: '18:00 - 20:00',
duration: '2 hours',
pax: 4,
details: [
{ label: 'Shuttle (prov)', value: '2 tubes' }
],
charge: 50.00
},
{
name: 'Swimming Pool',
facility: 'Olympic Pool',
timeSlot: '18:00 - 20:00',
duration: '2 hours',
pax: 2,
details: [],
charge: 20.00
},
{
name: 'Gym Access',
facility: 'Main Gym',
timeSlot: '18:00 - 20:00',
duration: '2 hours',
pax: 1,
details: [
{ label: 'Locker', value: 'L-045' }
],
charge: 15.00
}
],
subtotal: 85.00,
discountLabel: 'Gold Member 20%',
discount: 17.00,
tax: 4.08,
grandTotal: 72.08,
paymentMethod: 'Debit Card',
paymentRef: 'DC-99210045',
pointsEarned: 72,
totalPoints: 3480
});
12.11
SNOOKER TABLE SESSION TIMER SLIP
A small slip printed when a table session starts, given to the customer
as a reminder of their table assignment and start time. This uses partial
cut so slips stay attached in a roll for the counter staff.
function printTableStartSlip(session) {
var lines = [];
lines.push('##BEEP:1##');
lines.push('##CENTER####BOLD####BIG##Q-BALL SNOOKER');
lines.push('##SEP##');
lines.push('##CENTER####BOLD##TABLE SESSION STARTED');
lines.push('##SEP##');
lines.push('');
lines.push('##CENTER####BOLD####BIG##'
+ session.tableName);
lines.push('');
lines.push('##TABLE:<Type|>'
+ session.tableType + '##');
lines.push('##TABLE:<Player(s)|>'
+ session.playerCount + '##');
lines.push('');
lines.push('##DSEP##');
lines.push('##CENTER####BOLD##Start Time');
lines.push('##CENTER####BOLD####BIG##'
+ session.startTime);
lines.push('##DSEP##');
lines.push('');
lines.push('##TABLE:<Rate|>RM '
+ session.ratePerHour.toFixed(2) + '/hr##');
if (session.isHappyHour) {
lines.push('##CENTER####BOLD##HAPPY HOUR RATE APPLIED');
lines.push('##TABLE:<Normal Rate|>RM '
+ session.normalRate.toFixed(2) + '/hr##');
lines.push('##TABLE:<Your Rate|>RM '
+ session.ratePerHour.toFixed(2) + '/hr##');
}
lines.push('');
lines.push('##TABLE:<Session ID|>'
+ session.sessionId + '##');
lines.push('');
lines.push('##SEP##');
lines.push('##CENTER####SMALL##Present this slip at counter');
lines.push('##CENTER####SMALL##when your session ends.');
lines.push('##CENTER####SMALL##Timer runs until checkout.');
lines.push('');
lines.push('##BARCODE_HEIGHT:50##');
lines.push('##BARCODE_HRI:BELOW##');
lines.push('##BARCODE:CODE128:' + session.sessionId + '##');
lines.push('');
lines.push('##CENTER##Enjoy your game!');
lines.push('');
lines.push('##FEED:3##');
lines.push('##PARTCUT##');
window.postMessage({
type: 'VYROX_PRINTER_CONNECTOR_PRINT',
content: lines.join('\n')
}, '*');
}
// Usage:
printTableStartSlip({
tableName: 'TABLE 7',
tableType: 'Snooker (Full Size)',
playerCount: 2,
startTime: '20:30',
ratePerHour: 12.00,
isHappyHour: true,
normalRate: 18.00,
sessionId: 'QBS-20260204-0071'
});
13.
CSS CLASSES AUTO-DETECTION
If you use common CSS frameworks (Bootstrap, Tailwind), the extension
recognizes these classes and converts them to formatting:
Bootstrap:
text-center -> ##CENTER##
fw-bold -> ##BOLD##
Tailwind CSS:
text-center -> ##CENTER##
font-bold -> ##BOLD##
text-xl -> ##BIG##
text-2xl -> ##BIG##
Generic:
center -> ##CENTER##
bold -> ##BOLD##
big -> ##BIG##
large -> ##BIG##
title -> ##BIG##
total -> ##BIG##
Computed styles:
font-weight >= 700 -> ##BOLD##
font-size >= 20px -> ##BIG##
text-align: center -> ##CENTER##
14.
EXISTING PRINT BUTTON INTERCEPTION
If your page already has a print button, the extension can intercept it
and redirect the print action to the thermal printer instead of the
browser's print dialog.
The extension looks for these selectors:
#btn-print
#print-btn
.btn-print
.print-btn
[data-action="print"]
button[onclick*="print"]
If any of these buttons are found AND a print content element exists on
the page, clicking the button will:
1. Prevent the default action (e.g., window.print())
2. Extract the print content
3. Send it to the thermal printer
This means if your existing code has:
<button id="btn-print" onclick="window.print()">Print</button>
The extension will automatically intercept this and print to the thermal
printer instead of opening the browser print dialog.
If you do NOT want this interception, use a button ID/class that does
not match the above selectors.
15.
ERROR HANDLING
When using Method B (postMessage), there is no direct callback. The
extension shows toast notifications on the page:
Success: "Print Sent" - green toast, top-right corner
Error: "Print Error" or "Print Failed" - red toast with details
Common error messages and their solutions:
"Could not connect to VYROX Printer Connector. Is the native host
installed?"
Cause: The Chrome extension cannot reach the native host.
Fix: Start the VYROX_PrinterConnector.exe application.
"Extension Reloaded - Please refresh this page to reconnect to VYROX
Printer Connector."
Cause: The extension was updated or reloaded while the page was open.
Fix: Refresh the web page (F5).
"Native host disconnected without response"
Cause: The native host crashed or is not properly installed.
Fix: Re-run the installer (VYROX_PrinterConnector.exe -Install).
"Native host timeout (10s)"
Cause: The native host did not respond within 10 seconds.
Fix: Check if the printer is jammed or offline. Restart the
native host.
"Printer busy"
Cause: A previous print job is still being processed.
Fix: Wait a moment and try again.
"Empty content"
Cause: The content sent to the printer was empty.
Fix: Check that your print text is not blank.
"No print content element found on this page"
Cause: The floating button or popup print was used, but no
matching element was found.
Fix: Add id="vyrox_printer_connector" to your print container.
"Print content is empty"
Cause: A print content element was found but it had no text.
Fix: Make sure the element has visible text content.
For programmatic error handling, you can listen for messages back from
the extension (reserved for future support):
window.addEventListener('message', function(event) {
if (event.data
&& event.data.type === 'VYROX_PRINTER_CONNECTOR_RESULT') {
if (event.data.success) {
console.log('Print sent successfully');
} else {
console.error('Print failed:', event.data.error);
}
}
});
NOTE: The current version does not send result messages back via
postMessage. This is documented here for potential future support.
Currently, the extension shows toast notifications only.
16.
DEBUGGING AND TROUBLESHOOTING
Step 1: Check extension popup
Click the VYROX Printer Connector icon in Chrome toolbar.
- Native Host should show "Connected" (green dot)
- Printer should show the printer name (green dot)
- If either is red, the native host is not running or not installed.
Step 2: Check the log
In the extension popup, click "Toggle Log" to see debug messages.
Step 3: Check if print content element is detected
Open Chrome DevTools (F12) and run in the console:
document.querySelector('#vyrox_printer_connector')
If this returns null, the extension cannot find your print content
element. Try the other supported selectors:
document.querySelector('#vyrox-printer-connector')
document.querySelector('.vyrox_printer_connector')
document.querySelector('[data-vyrox-printer-connector="true"]')
document.querySelector('#print-area')
Step 4: Check if the floating button appears
If a print content element exists, a floating print button should
appear in the bottom-right corner of the page. If it does not:
- Check that the extension is enabled in chrome://extensions
- Check that content scripts are allowed on your page
- Try refreshing the page
- Check if another element with a higher z-index is covering it
Step 5: Test with a simple print
Add this to any page and check if printing works:
<div id="vyrox_printer_connector">Test print 123</div>
Step 6: Test postMessage directly
Open Chrome DevTools console and run:
window.postMessage({
type: 'VYROX_PRINTER_CONNECTOR_PRINT',
content: 'Hello from console\n##CUT##'
}, '*');
You should see a toast notification on the page.
Step 7: Check native host directly
Open the folder C:\VYROX_PrinterConnector
- printer_settings.json should exist (printer detected)
- If you create a print.this file manually with text content,
the native host should pick it up and print it within 1 second.
Step 8: Check for Chrome extension errors
Go to chrome://extensions
Find VYROX Printer Connector
Click "Errors" (if shown) to see any console errors
Click "Inspect views: service worker" to see background script logs
Step 9: Check native host process
Open Windows Task Manager (Ctrl+Shift+Esc)
Look for VYROX_PrinterConnector.exe in the processes list.
If it is not running, launch it from C:\VYROX_PrinterConnector.
Step 10: Test printer hardware
If the native host is connected but prints fail:
- Check that the USB cable is firmly connected
- Check that the printer is powered on
- Check that paper is loaded and cover is closed
- Check for blinking error lights on the printer
- Try power-cycling the printer (off for 10 seconds, then on)
17.
SECURITY CONSIDERATIONS
- The extension only listens for postMessage from the same window
(event.source === window check). Cross-origin iframes cannot trigger
printing.
- The native host only accepts three commands: ping, status, and print.
It does not execute arbitrary code.
- Print content is written to a temporary file (print.this) and
deleted immediately after printing.
- The native host communicates only with the locally connected USB
printer. It does not make any network connections.
- The extension runs on all URLs by default. If you want to restrict
it, you can modify the manifest.json matches pattern.
- All HTML content in toast notifications is escaped to prevent XSS.
- The native host runs under the current Windows user account. No
elevated privileges are required for normal operation (only for
initial installation).
18.
BROWSER COMPATIBILITY
Supported:
Google Chrome (version 88+, Manifest V3)
Microsoft Edge (Chromium-based, version 88+)
Not supported:
Firefox (different native messaging API)
Safari (no native messaging support)
Opera (may work with Chrome extension sideloading)
Operating system:
The native host requires Windows (PowerShell).
The Chrome extension itself works on any OS, but printing only works
on Windows where the native host is installed.
19.
ADVANCED INTEGRATION PATTERNS
19.1
AUTO-PRINT AFTER AJAX ORDER SUBMISSION
The most common integration pattern: print automatically after an order
is saved to your backend, with no user interaction required.
function saveAndPrint(orderData) {
fetch('/api/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(orderData)
})
.then(function(res) { return res.json(); })
.then(function(savedOrder) {
// Build print content from server response
var lines = [];
lines.push('##CENTER####BOLD####BIG##MY STORE');
lines.push('##SEP##');
lines.push('Bill No: ' + savedOrder.billNo);
lines.push('Date: ' + savedOrder.date);
lines.push('##SEP##');
savedOrder.items.forEach(function(item) {
lines.push('##TABLE:<' + item.name
+ '|^' + item.qty
+ '|>' + item.price.toFixed(2) + '##');
});
lines.push('##SEP##');
lines.push('##BOLD####TABLE:<TOTAL|>RM '
+ savedOrder.total.toFixed(2) + '##');
lines.push('##FEED:4##');
lines.push('##CUT##');
window.postMessage({
type: 'VYROX_PRINTER_CONNECTOR_PRINT',
content: lines.join('\n')
}, '*');
})
.catch(function(err) {
alert('Order save failed: ' + err.message);
});
}
19.2
PRINT QUEUE WITH MULTIPLE JOBS
When you need to print multiple slips from one action (for example a
customer copy plus a kitchen copy), add a delay between each job to
allow the printer to finish.
function printCustomerAndKitchenCopy(order) {
var customerCopy = buildCustomerBill(order);
var kitchenCopy = buildKitchenOrder(order);
// Print customer copy first
window.postMessage({
type: 'VYROX_PRINTER_CONNECTOR_PRINT',
content: customerCopy
}, '*');
// Wait 3 seconds then print kitchen copy
setTimeout(function() {
window.postMessage({
type: 'VYROX_PRINTER_CONNECTOR_PRINT',
content: kitchenCopy
}, '*');
}, 3000);
}
function buildCustomerBill(order) {
var lines = [];
lines.push('##CENTER####BOLD####BIG##MY RESTAURANT');
lines.push('##CENTER##CUSTOMER COPY');
lines.push('##DSEP##');
// ... build full bill ...
lines.push('##FEED:4##');
lines.push('##CUT##');
return lines.join('\n');
}
function buildKitchenOrder(order) {
var lines = [];
lines.push('##BEEP:2##');
lines.push('##CENTER####BOLD####BIG##KITCHEN');
lines.push('##DSEP##');
lines.push('##BOLD##Table: ' + order.tableNo);
// ... build kitchen items ...
lines.push('##FEED:3##');
lines.push('##PARTCUT##');
return lines.join('\n');
}
Recommended delays between print jobs:
Short content (under 20 lines): 2000ms (2 seconds)
Medium content (20-50 lines): 3000ms (3 seconds)
Long content or images/QR codes: 5000ms (5 seconds)
19.3
DUAL-COPY PRINTING (CUSTOMER + MERCHANT)
Print two copies in one print job by including both copies in a single
content string with a cut between them. This is faster than sending
two separate jobs.
function printDualCopy(order) {
var lines = [];
// --- MERCHANT COPY ---
lines.push('##CENTER####BOLD##** MERCHANT COPY **');
lines.push('##CENTER####BOLD####BIG##MY STORE');
lines.push('##DSEP##');
lines.push('Bill No: ' + order.billNo);
// ... full bill content ...
lines.push('##CENTER####SMALL##Merchant copy - do not discard');
lines.push('##FEED:4##');
lines.push('##CUT##');
// --- CUSTOMER COPY ---
lines.push('##CENTER####BOLD##** CUSTOMER COPY **');
lines.push('##CENTER####BOLD####BIG##MY STORE');
lines.push('##DSEP##');
lines.push('Bill No: ' + order.billNo);
// ... full bill content ...
lines.push('##CENTER##Thank you!');
lines.push('##FEED:4##');
lines.push('##CUT##');
window.postMessage({
type: 'VYROX_PRINTER_CONNECTOR_PRINT',
content: lines.join('\n')
}, '*');
}
19.4
CONDITIONAL PRINT BASED ON PRINTER STATUS
Before printing, you can check if the native host is connected by
sending a test message through the extension. This requires listening
for a response from the content script.
function checkPrinterAndPrint(content) {
// First test connection via the extension popup mechanism
chrome.runtime.sendMessage(
{ action: 'testConnection' },
function(response) {
if (response && response.success) {
// Printer is connected, proceed with print
window.postMessage({
type: 'VYROX_PRINTER_CONNECTOR_PRINT',
content: content
}, '*');
} else {
// Printer not available, show fallback
alert('Printer is not connected. Please check the printer.');
}
}
);
}
NOTE: The chrome.runtime.sendMessage API is only available within
the extension context. For web pages, rely on the toast notification
feedback from the extension to know if the print succeeded or failed.
19.5
FRAMEWORK INTEGRATION (REACT, VUE, ANGULAR)
React example:
function PrintButton({ orderData }) {
const handlePrint = () => {
const lines = [];
lines.push('##CENTER####BOLD####BIG##MY STORE');
lines.push('##SEP##');
orderData.items.forEach(item => {
lines.push('##TABLE:<' + item.name
+ '|^' + item.qty
+ '|>' + item.price.toFixed(2) + '##');
});
lines.push('##SEP##');
lines.push('##BOLD####TABLE:<TOTAL|>RM '
+ orderData.total.toFixed(2) + '##');
lines.push('##FEED:4##');
lines.push('##CUT##');
window.postMessage({
type: 'VYROX_PRINTER_CONNECTOR_PRINT',
content: lines.join('\n')
}, '*');
};
return <button onClick={handlePrint}>Print Bill</button>;
}
Vue example:
<template>
<button @click="printBill">Print Bill</button>
</template>
<script>
export default {
props: ['orderData'],
methods: {
printBill() {
const lines = [];
lines.push('##CENTER####BOLD####BIG##MY STORE');
lines.push('##SEP##');
this.orderData.items.forEach(item => {
lines.push('##TABLE:<' + item.name
+ '|^' + item.qty
+ '|>' + item.price.toFixed(2) + '##');
});
lines.push('##SEP##');
lines.push('##BOLD####TABLE:<TOTAL|>RM '
+ this.orderData.total.toFixed(2) + '##');
lines.push('##FEED:4##');
lines.push('##CUT##');
window.postMessage({
type: 'VYROX_PRINTER_CONNECTOR_PRINT',
content: lines.join('\n')
}, '*');
}
}
};
</script>
Angular example:
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-print-button',
template: '<button (click)="printBill()">Print Bill</button>'
})
export class PrintButtonComponent {
@Input() orderData: any;
printBill() {
const lines: string[] = [];
lines.push('##CENTER####BOLD####BIG##MY STORE');
lines.push('##SEP##');
this.orderData.items.forEach((item: any) => {
lines.push('##TABLE:<' + item.name
+ '|^' + item.qty
+ '|>' + item.price.toFixed(2) + '##');
});
lines.push('##SEP##');
lines.push('##BOLD####TABLE:<TOTAL|>RM '
+ this.orderData.total.toFixed(2) + '##');
lines.push('##FEED:4##');
lines.push('##CUT##');
window.postMessage({
type: 'VYROX_PRINTER_CONNECTOR_PRINT',
content: lines.join('\n')
}, '*');
}
}
Plain PHP (server-rendered page with print trigger):
<?php
$order = getOrderFromDatabase($orderId);
?>
<div id="vyrox_printer_connector" style="display:none;">
<pre class="print-content-text">
##CENTER####BOLD####BIG##MY STORE
##CENTER##123 Main Street
##DSEP##
##CENTER####BOLD##SALES BILL
##DSEP##
Bill No: <?= $order['bill_no'] ?>
Date: <?= $order['date'] ?>
##SEP##
<?php foreach ($order['items'] as $item): ?>
##TABLE:<<?= $item['name'] ?>|^<?= $item['qty'] ?>|><?= number_format($item['price'], 2) ?>##
<?php endforeach; ?>
##SEP##
##BOLD####BIG####TABLE:<TOTAL|>RM <?= number_format($order['total'], 2) ?>##
##SEP##
##TABLE:<<?= $order['payment_method'] ?>|><?= number_format($order['amount_paid'], 2) ?>##
##TABLE:<Change|><?= number_format($order['change'], 2) ?>##
##SEP##
##CENTER##Thank you!
##FEED:4##
##CUT##
</pre>
</div>
<button onclick="document.getElementById('vyrox_printer_connector')
.style.display='block'; setTimeout(function(){
document.getElementById('vyrox_printer_connector')
.style.display='none';}, 500);">
Print Bill
</button>
Explanation: The hidden div contains pre-formatted print content. When
the button is clicked, the div is briefly made visible so the extension
MutationObserver detects it and shows the floating print button.
Alternatively, use the postMessage method for a cleaner approach.
20.
FREQUENTLY ASKED QUESTIONS
Q: Do I need to install anything on my web server?
A: No. Everything runs client-side. Your server just serves the web page
as normal. The extension and native host are installed on the
end-user's computer.
Q: Can I print without showing any print content on the page?
A: Yes. Use Method B (postMessage) to send content directly. No HTML
element is needed on the page. No floating button will appear.
Q: Can I print automatically without user interaction?
A: Yes. Call window.postMessage with VYROX_PRINTER_CONNECTOR_PRINT from
your JavaScript at any time. For example, after saving an order via
AJAX, you can immediately trigger the print.
Q: Can I print multiple copies?
A: Yes. Two approaches:
1. Send two separate postMessage calls with a 2-3 second delay.
2. Include both copies in one content string with ##CUT## between
them (see section 19.3 for dual-copy example).
Q: Can I print to multiple printers?
A: The current version supports one printer at a time, as configured
in the native host. Multiple printer support may be added in future
versions.
Q: What if the printer is offline or disconnected?
A: The extension will show a red error toast notification. The print job
will fail. Your application should handle this gracefully (for
example show a retry button or queue the job for later).
Q: What is the maximum print content length?
A: There is no hard limit. The native host processes the content line
by line. However, very long content (thousands of lines) may take
longer to print. The native messaging channel has a 1MB message
size limit.
Q: Can I use this with React, Vue, or Angular?
A: Yes. The postMessage API works with any framework. Just call
window.postMessage from your component. See section 19.5 for
framework-specific examples.
Q: Can I print images like logos?
A: Yes, using the ##IMAGE:PATH## tag. The image file must exist on the
user's local computer. Common approach: include the logo in the
native host installation at C:\VYROX_PrinterConnector\logo.bmp.
Q: Does the extension work on mobile?
A: No. Chrome extensions with native messaging do not work on Android
or iOS. This is a desktop-only solution.
Q: Can I customize the floating button position or appearance?
A: The floating button is styled by the extension's content script.
It has a z-index of 999999 and is positioned at bottom: 24px,
right: 24px. To customize it, you would need to modify the
extension source code (content.js).
Q: My print output has special characters that do not display correctly.
A: Use the ##CODEPAGE## tag to set the correct character encoding.
Try ##CODEPAGE:CP1252## for Western European characters, or
##CODEPAGE:UTF8## if your printer supports UTF-8.
Q: Can I open the cash drawer without printing anything visible?
A: Yes. Send just the drawer command:
window.postMessage({
type: 'VYROX_PRINTER_CONNECTOR_PRINT',
content: '##DRAWER##\n##CUT##'
}, '*');
Q: How do I print a barcode for scanning at checkout?
A: Use the ##BARCODE## tag. CODE128 is the most versatile type:
##BARCODE_HRI:BELOW##
##BARCODE:CODE128:YOUR-DATA-HERE##
See section 9.7 for all barcode types and options.
Q: Can I use this for a snooker or pool hall billing system?
A: Yes. See sections 12.7, 12.9, and 12.11 for complete snooker and
billiard examples including session billing, tournament scorecards,
and table timer slips.
Q: Can I use this for a sports facility booking system?
A: Yes. See sections 12.6, 12.8, and 12.10 for complete sports booking
examples including single-activity bookings, membership discount
handling, and multi-activity complex billing.
Q: What HTML element IDs does the extension look for?
A: In priority order: #vyrox_printer_connector,
#vyrox-printer-connector, #vyrox_printer_connector-content,
#print-area. It also checks classes: .vyrox_printer_connector,
.vyrox-printer-connector, .vyrox_printer_connector-content,
.print-area. And the data attribute:
[data-vyrox-printer-connector="true"].
Q: What is the postMessage type string I need to use?
A: The exact string is: VYROX_PRINTER_CONNECTOR_PRINT
It is case-sensitive and must match exactly.
Q: Can I prevent the auto-cut at the end of a print?
A: If your content includes any ##CUT## or ##PARTCUT## tag anywhere,
the extension will not add an automatic cut at the end. So place
your own ##CUT## or ##PARTCUT## exactly where you want it.
Q: The floating button is covering my page content. How do I move it?
A: The button is fixed-positioned at bottom: 24px, right: 24px. You
can add CSS on your page to reposition it:
#vyrox-printer-connector-fab { bottom: 80px !important; }
Q: How long does the printer take to process a print job?
A: Typical print jobs (under 50 lines) process in under 1 second.
Jobs with images or many QR codes may take 2-5 seconds. Jobs with
barcodes and QR codes together may take 3-7 seconds depending on
the printer model.
END OF INTEGRATION GUIDE
For support: https://vyrox.com
================================================================================