MobiWeb Help

Bluetooth Direct Printing

Bridges for connecting to Bluetooth thermal/receipt printers and sending raw ESC/POS or TSPL data directly - no WiFi required.

  • Android uses Bluetooth Classic (SPP - Serial Port Profile) via BluetoothSocket, and BLE via BluetoothGatt.

  • iOS uses BLE (CoreBluetooth) exclusively via AppleBluetoothPrint. Bluetooth Classic (SPP) is not available to third-party apps on iOS.

Android

Bridge object: AndroidBluetoothPrint

Methods

Method

Return

Description

isAvailable()

string

Returns "true" if Bluetooth is on, "false" otherwise

test()

string

Returns "AndroidBluetoothPrint bridge is accessible"

getPairedDevices()

void

Emits mobiweb:btPairedDevices with bonded devices

scan()

void

Starts Bluetooth Classic + BLE discovery simultaneously; emits mobiweb:btScanResult per device

stopScan()

void

Stops discovery

connect(macAddress)

void

Connects via SPP or BLE to the given MAC address

disconnect()

void

Disconnects the current printer

printRaw(base64Data)

void

Sends raw bytes (ESC/POS or TSPL) to the printer

printText(text\|optionsJson)

void

Sends UTF-8 text with ESC/POS formatting; accepts a plain string or a JSON options object

printTspl(optionsJson)

void

Prints a text label using auto-generated TSPL commands

printTsplRaw(tsplCommands)

void

Sends a raw TSPL command string directly to the printer

printHtml(optionsJson)

void

Renders HTML as a monochrome bitmap and prints it via the TSPL BITMAP command

isConnected()

string

Returns "true"/"false"

getConnectedDevice()

string

JSON string with device info, or ""

getDeviceServices()

string

Returns BLE GATT services JSON (BLE connections only)

printCpcl(optionsJson)

void

Prints a CPCL label using auto-generated CPCL commands

printCpclRaw(cpclCommands)

void

Sends a raw CPCL command string directly to the printer

printDiagnostic()

void

Sends TSPL SELFTEST + TSPL/CPCL/ESC-POS test labels; emits mobiweb:btDiagnostic per step

Events

Event name

event.detail shape

Description

mobiweb:btPairedDevices

{ devices: [{ address, name, bondState, type }] }

List of paired devices

mobiweb:btScanStarted

{}

Discovery has started

mobiweb:btScanResult

{ address, name, bondState, type }

A device was found

mobiweb:btScanFinished

{ count: number }

Discovery finished (BLE auto-stops after 12s)

mobiweb:btConnected

{ address, name, transport: "classic" \| "ble" }

Connected to printer

mobiweb:btDisconnected

{ reason?: string }

Disconnected from printer

mobiweb:btPrintSuccess

{ bytes: number, type?: string, transport?: "classic" \| "ble" }

Data sent successfully

mobiweb:btError

{ error: string }

An error occurred

mobiweb:btPermissionRequired

{ method: string }

Bluetooth permissions needed

mobiweb:btDiagnostic

{ test: string, status: string, bytes?: number, error?: string }

Emitted for each step of printDiagnostic()

mobiweb:btPrintSuccess type values:

Value

Triggered by

"text"

printText()

"tspl"

printTspl()

"tspl_raw"

printTsplRaw()

"cpcl"

printCpcl()

"cpcl_raw"

printCpclRaw()

"html_tspl"

printHtml() when protocol is "tspl" (default)

"html_cpcl"

printHtml() when protocol is "cpcl"

Device Type Values

Value

Meaning

0

Unknown

1

Bluetooth Classic

2

BLE

3

Dual (Classic + BLE)

printText(text | optionsJson)

Accepts either a plain string (legacy, prints as-is with one line feed) or a JSON options object:

Field

Type

Default

Description

text

string

-

(required) Text to print; use \n for new lines

init

boolean

false

Send ESC @ initialize command before printing

align

string

"left"

"left", "center", or "right"

bold

boolean

false

Bold text

underline

boolean

false

Underline text

fontSize

number

1

1 = normal, 2 = double, 3 = triple (clamped to 1-3)

feedLines

number

1

Blank lines to feed after text

cut

boolean

false

Send partial paper cut after printing

// Plain string (legacy) AndroidBluetoothPrint.printText('Hello World\n'); // Options object AndroidBluetoothPrint.printText(JSON.stringify({ text: 'Order #1042\nThank you!', align: 'center', bold: true, fontSize: 2, feedLines: 3, cut: true }));

printTspl(optionsJson)

Generates and sends a complete TSPL label job. The bridge handles SIZE, GAP, CLS, TEXT, and PRINT automatically.

Field

Type

Default

Description

text

string

-

(required) Label text; use \n for multiple lines

widthMm

number

40

Label width in mm

heightMm

number

30

Label height in mm

gapMm

number

2

Gap between labels in mm

align

string

"left"

Horizontal alignment: "left", "center", or "right"

vAlign

string

"top"

Vertical alignment of text block: "top", "middle", "bottom"

x

number

(auto)

Explicit X position in dots; overrides align

y

number

(auto)

Explicit Y position in dots; overrides vAlign

font

string

"2"

TSC built-in font name: "1"-"5" (see table below)

fontSize

number

1

Scale multiplier applied on top of base font: 1-10

qty

number

1

Number of copies to print

TSC built-in fonts:

font

Dimensions

Description

"1"

8x12 dots

Small mono

"2"

12x20 dots

Medium mono (default)

"3"

16x24 dots

Large mono

"4"

24x32 dots

Larger mono

"5"

32x48 dots

Largest mono

fontSize is a multiplier - e.g. font: "2" + fontSize: 2 = 24x40 dots per character.

AndroidBluetoothPrint.printTspl(JSON.stringify({ text: 'Hello World\nOrder #1042', widthMm: 60, heightMm: 40, gapMm: 2, align: 'center', vAlign: 'middle', font: '2', fontSize: 1, qty: 1 })); window.addEventListener('mobiweb:btPrintSuccess', (e) => { console.log('TSPL printed:', e.detail.bytes, 'bytes via', e.detail.transport); });

printTsplRaw(tsplCommands)

Sends a raw TSPL command string directly to the printer with no modification. Use this for full TSPL control - barcodes, QR codes, images, and custom layouts.

  • Each command must be terminated with \r\n.

  • The bridge sends the string as UTF-8 bytes over SPP or BLE.

const tspl = [ 'SIZE 60 mm,40 mm', 'GAP 3 mm,0 mm', 'DIRECTION 1,0', 'REFERENCE 0,0', 'CLS', 'TEXT 10,10,"3",0,1,1,"Hello World"', 'BARCODE 10,80,"128",60,1,0,2,2,"ABC123"', 'PRINT 1,1' ].join('\r\n') + '\r\n'; AndroidBluetoothPrint.printTsplRaw(tspl); window.addEventListener('mobiweb:btPrintSuccess', (e) => { console.log('Raw TSPL sent:', e.detail.bytes, 'bytes'); });

printCpcl(optionsJson)

Generates and sends a complete CPCL label job. CPCL (Comtec Printer Control Language) is used by Zebra, Intermec, Honeywell, and many portable label printers.

Field

Type

Default

Description

text

string

-

(required) Label text; use \n for multiple lines

heightDots

number

240

Label height in dots

dpi

number

200

Printer DPI: 200 or 300

qty

number

1

Number of copies

offset

number

0

Left margin offset in dots

x

number

10

Text X position in dots

y

number

10

Starting text Y position in dots

font

string

"4"

Fixed font 0-7 (see table below)

fontSize

number

0

Point size for scalable fonts; 0 uses fixed font size

setMagX

number

1

Horizontal magnification 1-16

setMagY

number

1

Vertical magnification 1-16

align

string

"left"

"left", "center", or "right"

CPCL fixed fonts (approx. dot heights at 200 DPI):

font

Approx. height

Description

"0"

18 dots

Smallest

"1"

20 dots

Small

"2"

24 dots

Medium

"3"

30 dots

Medium-large

"4"

40 dots

Large (default)

"5"

48 dots

Larger

"6"

56 dots

Even larger

"7"

80 dots

Largest

AndroidBluetoothPrint.printCpcl(JSON.stringify({ text: 'Order #1042\nItem: Widget Pro', heightDots: 240, dpi: 200, qty: 1, font: '4', align: 'center' }));

printCpclRaw(cpclCommands)

Sends a raw CPCL command string directly to the printer with no modification. Use for full CPCL control - barcodes, QR codes, images, boxes, and custom layouts.

  • The label must start with ! {offset} {hDpi} {vDpi} {heightDots} {qty} and end with PRINT\r\n.

  • All commands must be uppercase; each line must be terminated with \r\n.

  • Add PREFEED {n}\r\n after the header line to feed n dots forward before your content.

  • Add POSTFEED {n}\r\n before PRINT\r\n to feed n dots forward after your content.

const cpcl = [ '! 0 200 200 240 1', 'PREFEED 16', 'CENTER', 'TEXT 4 0 0 10 Order #1042', 'TEXT 4 0 0 60 Item: Widget Pro', 'POSTFEED 84', 'PRINT' ].join('\r\n') + '\r\n'; AndroidBluetoothPrint.printCpclRaw(cpcl); window.addEventListener('mobiweb:btPrintSuccess', (e) => { console.log('Raw CPCL sent:', e.detail.bytes, 'bytes'); });

printHtml(optionsJson)

Renders an HTML string in a hidden WebView at the exact label dimensions, captures it as a monochrome bitmap, then sends it to the printer using the TSPL BITMAP command. Rendered at 203 DPI (8 dots/mm).

Field

Type

Default

Description

html

string

-

(required) HTML content to render

widthMm

number

40

Label width in mm

heightMm

number

30

Label height in mm

gapMm

number

2

Gap between labels in mm

qty

number

1

Number of copies

threshold

number

128

Grayscale threshold for black/white conversion (0-255; lower = more black)

invert

boolean

false

Standard TSC TSPL spec uses 0 = white, 1 = black. Most MHT and Chinese clone printers use the opposite. Set invert: true if your label prints as a solid black rectangle. Applies only when protocol: "tspl".

protocol

string

"tspl"

Print protocol: "tspl" or "cpcl"

dpi

number

200

Printer DPI: 200 or 300 (any other value defaults to 200)

offset

number

0

Left margin offset in dots

preFeedDots

number

0

Inserts PREFEED {n} in the CPCL header, feeding media forward by n dots before the bitmap graphic. Omitted when 0. Only applies when protocol: "cpcl".

postFeedDots

number

0

Inserts POSTFEED {n} just before PRINT, feeding media forward by n dots after the bitmap. Omitted when 0. Only applies when protocol: "cpcl".

cpclBitmapEncoding

string

"eg"

CPCL bitmap encoding: "eg" (EG hex, universal), "gw" (GW raw binary), "cg" (CG PackBits RLE) - only used when protocol: "cpcl"

const html = ` <div style="font-family:monospace;font-size:14px;text-align:center;padding:4px;"> <strong>Order #1042</strong><br/> Item: Widget Pro<br/> Qty: 3 </div> `; AndroidBluetoothPrint.printHtml(JSON.stringify({ html, widthMm: 60, heightMm: 40, gapMm: 2, qty: 1, threshold: 128, invert: false })); window.addEventListener('mobiweb:btPrintSuccess', (e) => { console.log('HTML label printed:', e.detail.bytes, 'bytes via', e.detail.transport); });

getDeviceServices()

Returns a JSON string listing all discovered GATT services and characteristics for the currently connected BLE device. Only available when connected via BLE.

Return shape:

{ "services": [ { "uuid": "0000ff00-0000-1000-8000-00805f9b34fb", "characteristics": [ { "uuid": "0000ff02-0000-1000-8000-00805f9b34fb", "properties": 12, "canWrite": true } ] } ] }

Returns { "error": "Not connected via BLE" } if not connected via BLE.

const servicesJson = AndroidBluetoothPrint.getDeviceServices(); const { services } = JSON.parse(servicesJson); services.forEach(s => { console.log('Service:', s.uuid); s.characteristics.forEach(c => { console.log(' Char:', c.uuid, 'canWrite:', c.canWrite); }); });

printDiagnostic()

Sends a sequence of test prints to verify the printer connection and determine which protocol the printer understands. Runs TSPL SELFTEST, a TSPL label, a CPCL label, and an ESC/POS text block in order.

Emits mobiweb:btDiagnostic after each step:

{ "test": "tspl_selftest", "status": "sent" } { "test": "tspl_label", "status": "sent", "bytes": 120 } { "test": "cpcl_label", "status": "sent", "bytes": 80 } { "test": "escpos_text", "status": "sent", "bytes": 24 } { "test": "complete", "status": "all_tests_sent" }

On failure, status will be "failed" and an "error" field is included. Check logcat for detailed output.

AndroidBluetoothPrint.printDiagnostic(); window.addEventListener('mobiweb:btDiagnostic', (e) => { console.log('Diagnostic:', e.detail.test, '-', e.detail.status); });

Full Connection Example

// 1. Scan for devices AndroidBluetoothPrint.scan(); window.addEventListener('mobiweb:btScanResult', (e) => { console.log('Found:', e.detail.name, e.detail.address); }); window.addEventListener('mobiweb:btScanFinished', (e) => { console.log('Scan done, found', e.detail.count, 'devices'); }); // 2. Connect AndroidBluetoothPrint.connect('00:11:22:33:44:55'); window.addEventListener('mobiweb:btConnected', (e) => { console.log('Connected to', e.detail.name, 'via', e.detail.transport); // 3a. ESC/POS text AndroidBluetoothPrint.printText(JSON.stringify({ text: 'Receipt\nOrder #1042', align: 'center', bold: true, cut: true })); // 3b. TSPL label AndroidBluetoothPrint.printTspl(JSON.stringify({ text: 'Product\nSKU-001', widthMm: 60, heightMm: 40, align: 'center', vAlign: 'middle', font: '3', qty: 2 })); // 3c. Raw TSPL AndroidBluetoothPrint.printTsplRaw( 'SIZE 60 mm,40 mm\r\nGAP 2 mm,0 mm\r\nCLS\r\nTEXT 10,10,"2",0,1,1,"Raw Label"\r\nPRINT 1,1\r\n' ); }); // 4. Handle errors window.addEventListener('mobiweb:btError', (e) => { console.error('BT Error:', e.detail.error); }); // 5. Disconnect AndroidBluetoothPrint.disconnect();

iOS

Bridge object: AppleBluetoothPrint (injected shim)

Methods

Method

Description

isAvailable()

Checks Bluetooth state; emits mobiweb:btAvailability

scan()

Starts BLE discovery; auto-stops after 12 s; emits mobiweb:btScanStarted, mobiweb:btScanResult per device, then mobiweb:btScanFinished

stopScan()

Stops BLE scan early

connect(uuid)

Connects to BLE peripheral by UUID; emits mobiweb:btConnected or mobiweb:btError

disconnect()

Disconnects; emits mobiweb:btDisconnected

printRaw(base64Data)

Sends raw bytes (base64-encoded, same as Android)

printText(optionsJson)

ESC/POS formatted text (same options as Android)

printTspl(optionsJson)

TSPL label printing (same options as Android)

printTsplRaw(tsplCommands)

Raw TSPL string (same as Android)

printCpcl(optionsJson)

CPCL label printing (same options as Android)

printCpclRaw(cpclCommands)

Raw CPCL string (same as Android)

printHtml(optionsJson)

HTML-to-bitmap label printing (same options as Android + compress)

isConnected()

Checks connection state; emits mobiweb:btConnectionStatus

getConnectedDevice()

Emits mobiweb:btConnectedDevice with device info

getDeviceServices()

Emits mobiweb:btDeviceServices with GATT services JSON

printDiagnostic()

Sends TSPL/CPCL/ESC-POS test prints; emits mobiweb:btDiagnostic per step

Events

Event name

event.detail shape

Description

mobiweb:btAvailability

{ available: boolean }

Result of isAvailable()

mobiweb:btScanStarted

{ ble: true }

BLE scan has started

mobiweb:btScanResult

{ address: string, name: string, ... }

A BLE device was found (address is the peripheral UUID)

mobiweb:btScanFinished

{ count: number }

Scan complete

mobiweb:btConnected

{ address, name, transport: "ble", mtu, writeCharacteristic }

Connected

mobiweb:btDisconnected

{ reason?: string }

Disconnected

mobiweb:btConnectionStatus

{ connected: boolean }

Result of isConnected()

mobiweb:btConnectedDevice

device info object

Result of getConnectedDevice()

mobiweb:btDeviceServices

{ services: [...] }

Result of getDeviceServices()

mobiweb:btPrintSuccess

{ bytes: number, type?: string, transport: "ble" }

Data sent successfully

mobiweb:btDiagnostic

{ test: string, status: string, bytes?: number, error?: string }

Diagnostic step result

mobiweb:btError

{ error: string }

An error occurred

printHtml - iOS-only extra option

In addition to all the same options as Android, printHtml on iOS accepts one extra field:

Field

Type

Default

Description

compress

boolean

true

Use zlib compression for TSPL BITMAP mode 10. Set false for printers that don't support compressed TSPL bitmaps.

Key Differences from Android

Feature

Android

iOS

Transport

Classic (SPP) + BLE

BLE only

connect() argument

MAC address ("AA:BB:CC:DD:EE:FF")

BLE UUID ("XXXXXXXX-XXXX-...")

isAvailable()

Returns "true"/"false" string

Emits mobiweb:btAvailability event

isConnected()

Returns "true"/"false" string

Emits mobiweb:btConnectionStatus event

getConnectedDevice()

Returns JSON string

Emits mobiweb:btConnectedDevice event

getDeviceServices()

Returns JSON string

Emits mobiweb:btDeviceServices event

getPairedDevices()

Supported

Not available (BLE has no classic pairing)

TSPL bitmap compression

Not configurable

compress option in printHtml

Full Connection Example (iOS)

// 1. Check availability AppleBluetoothPrint.isAvailable(); window.addEventListener('mobiweb:btAvailability', (e) => { if (!e.detail.available) console.warn('Bluetooth is off'); }); // 2. Scan AppleBluetoothPrint.scan(); window.addEventListener('mobiweb:btScanResult', (e) => { console.log('Found:', e.detail.name, e.detail.address); // address = BLE UUID }); window.addEventListener('mobiweb:btScanFinished', (e) => { console.log('Scan done,', e.detail.count, 'devices found'); }); // 3. Connect (use UUID from scan result) AppleBluetoothPrint.connect('12345678-1234-1234-1234-123456789ABC'); window.addEventListener('mobiweb:btConnected', (e) => { console.log('Connected via', e.detail.transport, 'MTU:', e.detail.mtu); // 4a. TSPL label AppleBluetoothPrint.printTspl(JSON.stringify({ text: 'Product\nSKU-001', widthMm: 60, heightMm: 40, align: 'center', vAlign: 'middle', font: '2', qty: 1 })); // 4b. HTML label AppleBluetoothPrint.printHtml(JSON.stringify({ html: '<div style="text-align:center"><b>Order #1042</b></div>', widthMm: 60, heightMm: 40, qty: 1, compress: true })); }); // 5. Handle errors window.addEventListener('mobiweb:btError', (e) => { console.error('BT Error:', e.detail.error); }); // 6. Disconnect AppleBluetoothPrint.disconnect();
03 May 2026