Native Bridge Chrome · Edge · Brave · Chromium

Your browser can finally
talk to local TCP.

Local TCP Bridge is a Native Messaging host that lets web apps stream raw bytes straight to local hardware, like ESC/POS thermal printers, over a secure, millisecond-latency binary protocol. No cloud hop, no print server.

One-click installers MV3 · Native Messaging Auto-installs Node.js MIT Licensed
Request path · web → hardware
Web App postMessage
Extension allowlist gate
Native Host node · stdio
Printer TCP :9100
What it is

The missing link between web browsers and local hardware

Browsers deliberately can't open raw TCP sockets. Local TCP brings that capability back, safely, by pairing a lightweight Chrome extension with a tiny Node.js native host that does the socket work on your machine.

One-click setup

Native installers for macOS .pkg, Windows .exe & Linux .run. No terminal needed. The installer registers everything and auto-installs Node.js if it's missing.

Enterprise security

Chrome's Native Messaging sandbox plus a configurable origin allowlist, so you can lock the bridge down to only your own web apps, straight from the popup.

Binary performance

Streams raw ESC/POS bytes with millisecond precision. Concurrent jobs are correlated by reqId and serialized per socket, so byte streams never interleave.

Framework agnostic

Works with Flutter Web, React, Vue, Angular or plain JS. The content script bridges every page, with no SDK and no globals to load.

macOS-privacy aware

Runs through the system node binary. That is exactly what keeps it working under macOS 15/26 Local Network Privacy, where an unsigned standalone binary gets silently blocked from the LAN.

Self-healing host

Under MV3 the service worker can sleep. PRINT/SEND re-dial the socket automatically, so printing keeps working with no manual reconnect.

How it works

A multi-layer relay

Each request is tagged, gated, and correlated on its way from the page to the printer. The response then travels the same wire back.

1

Web App

Sends a window.postMessage tagged with a unique messageId to the injected content script.

2

Content Script

Forwards the message to the extension background, then posts the reply back scoped to the page's own origin, never a * wildcard.

3

Background (Service Worker)

Checks the origin allowlist, then relays the request (tagged with a reqId) to the Native Messaging host over a single persistent port.

4

Native Host (Node.js)

Opens a raw TCP socket to your hardware on port 9100, writes the bytes, and echoes the reqId back so concurrent jobs never cross wires.

com.algoramming.localtcp
Web page
source: "localtcp_req"
Extension worker
allowlist · reqId tagging
node index.js
4-byte len prefix + JSON
Thermal printer
raw TCP socket · :9100
Quick start

Two ways to talk to the bridge

Use plain JavaScript on any web framework, or the official Flutter package for one API across web, mobile & desktop. Always check availability first with CHECK_BRIDGE.

1 · Drop-in client

A tiny promise-based wrapper. No imports and no globals: the content script relays window.postMessage to the host.

JS localtcp.js
export class LocalTcp {
  constructor({ timeoutMs = 30000 } = {}) {
    this._pending = new Map();
    window.addEventListener('message', (e) => {
      const d = e.data;
      if (!d || d.source !== 'localtcp_res') return;
      const p = this._pending.get(d.messageId);
      if (!p) return;
      clearTimeout(p.timer);
      p.resolve(d.response);
    });
  }
  /* → {success, connected, version} */
  checkBridge()  { return this._send({ action: 'CHECK_BRIDGE' }); }
  print(host, port, bytes) {
    return this._send({ action: 'PRINT', host, port, data: bytes });
  }
}

2 · Print a receipt

Encode ESC/POS bytes with any encoder, confirm the bridge, then send a plain array.

JS receipt.js
import EscPosEncoder from 'esc-pos-encoder';
import { LocalTcp } from './localtcp.js';

const printer = new LocalTcp();

// 1 · make sure the bridge is ready
const bridge = await printer.checkBridge();
if (!bridge.connected) return alert('Install Local TCP first.');

// 2 · build the receipt
const data = new EscPosEncoder()
  .initialize().align('center').bold(true)
  .line('ALGORAMMING CAFE').newline().cut()
  .encode();

// 3 · print a PLAIN array, then close
await printer.print('192.168.1.50', 9100, Array.from(data));
Always pass a plain Array<number>, using Array.from(uint8array). A raw Uint8Array doesn't survive the extension's JSON message hop and arrives malformed.

One API, every platform

The official package auto-detects the platform: on web it routes through this extension; on mobile/desktop it opens a direct socket. Your code is identical everywhere.

YAML pubspec.yaml
dependencies:
  flutter_esc_pos_network_universal: ^1.1.0
PlatformTransportExtension?
Android · iOS · Win · macOS · LinuxDirect TCPNot needed
WebThis extensionRequired

Print raw ESC/POS bytes

printTicket handles connect → print → disconnect. Give web extra timeout to wake a sleeping Wi-Fi printer.

DART print_receipt.dart
final printer = PrinterNetworkManager(
  '192.168.1.50', port: 9100,
  paperSize: ThermalPosPrinterPageSize.size80mm,
  timeout: const Duration(seconds: 30),
);

final g = Generator(PaperSize.mm80, profile);
final bytes = <int>[
  ...g.text('ALGORAMMING CAFE',
      styles: const PosStyles(bold: true)),
  ...g.feed(2), ...g.cut(),
];

final result = await printer.printTicket(bytes);
printer.dispose(); // closes socket / removes web listener
You can also print any Flutter widget as a receipt with printWidget(context, child: …). It renders to a bitmap and sends it as a single ESC/POS raster image, which is perfect for logos and QR codes. Package: flutter_esc_pos_network_universal.
Reference

Message protocol

The full contract for any client. Post to window with source: "localtcp_req" and a unique messageId; the response returns on a message event with source: "localtcp_res" and the same id.

→ Request

FieldTypeNotes
sourcestringmust be "localtcp_req"
messageIdstringunique id, echoed back
actionstringsee actions below
hoststringprinter IP on the LAN
portnumberdefault 9100
datanumber[]ESC/POS bytes, a plain array
readTimeoutMsnumberoptional; wait for a device reply

← Response

FieldTypeNotes
successbooloverall result
connectedboolCHECK_BRIDGE: host reachable
versionstringinstalled host version
bytesSentnumberPRINT / SEND
datanumber[]bytes read back on readTimeoutMs
errorstringpresent on failure
CHECK_BRIDGECONNECTPRINTSENDDISCONNECTPING

A high-level alias is also accepted: { type: "LOCAL_TCP_PRINT", payload: { host, port, bytes, readTimeoutMs } }, where bytes maps to data. Concurrent jobs to the same printer are serialized so byte streams never interleave.

Installation guide

One installer. No terminal.

Add the extension, then run the one-click installer for your OS. It registers the native host with every Chromium browser and auto-installs Node.js if it isn't already present. We've selected your platform below.

Package · localtcp-mac-installer.pkg Download .pkg
  1. Add the extension

    Install Local TCP from the Chrome Web Store. On first install it opens a setup tab and starts your download automatically.

  2. Open the installer

    Double-click localtcp-mac-installer.pkgContinueInstall. macOS prompts for your password, then the host installs for your user account.

  3. Restart Chrome

    Quit Chrome completely and reopen it. The popup flips to Bridge Linked on its own, with no manual re-check.

  4. Test the connection

    Open the popup, enter your printer's IP and port (usually 9100), and hit Test Connection.

Running through the system node keeps LAN access working under macOS 15/26 Local Network Privacy. If Node isn't installed, the installer fetches it for you.

Installed to: ~/Library/Application Support/LocalTCP/index.js · manifest registered in each browser's NativeMessagingHosts/

Installer · localtcp-windows-installer.exe Download .exe
  1. Add the extension

    Install Local TCP from the Chrome Web Store, then download the installer from the popup's Download Setup Kit button.

  2. Run the installer

    Double-click localtcp-windows-installer.exeInstall. No admin rights needed; it installs per-user.

  3. Restart Chrome

    Close every Chrome window and reopen. The popup shows Bridge Linked automatically.

  4. Test the connection

    Enter the printer IP and port in the popup and click Test Connection.

The installer is unsigned, so SmartScreen may warn you. Click More info → Run anyway to proceed. Node.js is auto-installed via winget if it's missing.

Installed to: %APPDATA%\Algoramming\LocalTCP\run_bridge.bat · registered under HKCU registry keys

Installer · localtcp-linux-installer.run Download .run
  1. Add the extension

    Install Local TCP from the Chrome Web Store and download the installer from the popup.

  2. Make it executable & run

    From your download folder:

    chmod +x localtcp-linux-installer.run
    ./localtcp-linux-installer.run
  3. Restart the browser

    Fully quit Chrome/Chromium/Brave and reopen. The popup reports Bridge Linked.

  4. Test the connection

    Set the printer IP/port in the popup and press Test Connection.

Node.js is auto-installed via your system package manager if it's missing. Covers Chrome, Chromium, Edge & Brave.

Installed to: ~/.local/lib/algoramming/localtcp/index.js · manifest registered in each browser's NativeMessagingHosts/

Uninstallation guide

Just as easy to remove

In the extension popup, click Uninstall Setup Kit to download the uninstaller for your OS, then run it. To remove the extension itself, use Remove Extension in the popup.

Uninstaller · localtcp-mac-uninstaller.pkg Download uninstaller
  1. Get the uninstaller

    Open the popup → Uninstall Setup Kit, or download the .pkg above.

  2. Run it

    Double-click localtcp-mac-uninstaller.pkgContinueInstall → enter your password → Done. It removes the host and browser registrations.

  3. Remove the extension (optional)

    Click Remove Extension in the popup, or manage it at chrome://extensions.

Uninstaller · localtcp-windows-uninstaller.exe Download uninstaller
  1. Three ways to remove

    Run localtcp-windows-uninstaller.exe, or Start Menu → Uninstall Local TCP Bridge, or Settings → Apps → Local TCP → Uninstall.

  2. Confirm

    The uninstaller removes the host files and clears the HKCU registry keys for every browser.

  3. Remove the extension (optional)

    Use Remove Extension in the popup, or chrome://extensions.

Uninstaller · localtcp-linux-uninstaller.run Download uninstaller
  1. Get the uninstaller

    From the popup → Uninstall Setup Kit, or download the .run above.

  2. Make it executable & run

    chmod +x localtcp-linux-uninstaller.run
    ./localtcp-linux-uninstaller.run
  3. Remove the extension (optional)

    Click Remove Extension in the popup, or open chrome://extensions.

Local TCP Bridge is built and maintained by Algoramming Systems Ltd., a software house that specialises in connecting modern web applications with the physical hardware that runs real businesses: point-of-sale, thermal printing, and networked devices.

v2.1.1Current release
5+Browsers supported
MITOpen-source license
Chrome Web Store · Local TCP
Flutter package · pub.dev
The inspiration

Built to power Algonize

Local TCP Bridge began as a piece of Algonize, a business platform that needed the browser to print straight to local hardware. Solving that once turned into a tool anyone can use, so we opened it up.

Explore Algonize www.algonize.xyz