Quickstart

By the end of this page a web page you control will put a receipt out of a thermal printer, with no print dialog and no PDF download in between.

1. Install the bridge on the machine with the printer

Download and run the installer. It sets itself to start at login and begins listening on the first free port from 47600. Open Settings → Printers, add the printer, tell it which paper is loaded, and press Print test page. If that does not produce paper, stop here — nothing further will work, and the troubleshooting guide is the next page to read.

2. Load the SDK

It is one ES module with no dependencies and no build step. Import it from the CDN, or copy the file into your project — it is right here and it is about four hundred readable lines.

index.html
<!-- Nothing to npm install. One module, off the CDN. -->
<script type="module">
  import { connect } from 'https://printol.crawlink.com/sdk/printol.js';
</script>

3. Connect

connect() discovers the bridge, reuses a stored token if this browser has paired with this machine before, and otherwise runs the pairing handshake. The first time, a dialog appears on the counter screen showing your app name, your origin and six digits; somebody clicks Allow. After that it is silent forever.

connect.js
import { connect } from 'https://printol.crawlink.com/sdk/printol.js';

const printol = await connect({
  appName: 'Anand Traders Billing',   // shown on the approval dialog
  scopes: ['print'],                  // ask for the least you need
  onCode: (code) => showToUser(code)  // the six digits on the counter screen
});

// What is attached, and what paper each one is loaded with.
const printers = await printol.printers();
console.table(printers);
Call connect() in response to something the user did, not on page load. The pairing dialog appearing on a shop's counter for no reason is how you get uninstalled.

4. Print

You send business data and a template id. Not HTML, not ESC/POS. The bridge owns the layout, which is why the same call works whether that printer has a 58mm roll or A4 in the tray.

print.js
const bill = {
  number: 'INV-2043',
  date: new Date().toISOString(),
  seller:   { name: 'Anand Traders', gstin: '33ABCDE1234F1Z5' },
  customer: { name: 'Ravi Kumar', phone: '+91 90000 55555' },
  lines: [
    { name: 'Masala Dosa',   qty: 2, rate: 90,  tax_percent: 5 },
    { name: 'Filter Coffee', qty: 3, rate: 40,  tax_percent: 5 },
    { name: 'Curd Rice',     qty: 1, rate: 110, tax_percent: 5 }
  ],
  payment: { mode: 'upi', reference: 'YBL2209' },
  footer: 'Thank you. Visit again.'
};

const job = await printol.print({
  templateId: 'pos.receipt.retail',
  data: bill,
  options: { cut: true, idempotency_key: bill.number }
});

// print() resolves when the job is queued, not when paper appears.
const finished = await printol.waitForJob(job.job_id);
console.log(finished.status);   // 'done' | 'failed'

5. Show a preview first

preview() takes the same inputs and returns the rendering instead of queueing it — text for exactly the characters the thermal printer will receive, html for something you can style into a pane, pdf for a base64 document.

preview.js
// Same inputs, no paper. Perfect for a preview pane and for tests.
const { content, columns } = await printol.preview({
  templateId: 'pos.receipt.retail',
  paper: 'pos80',
  format: 'text',
  data: bill
});

document.querySelector('pre.preview').textContent = content;

Handling the things that go wrong

Every failure arrives as a PrintolError with a code you can branch on. Four of them mean genuinely different things to your user, and the difference matters: “Printol is not installed” and “the printer is unplugged” need different sentences on screen.

errors.js
try {
  await printol.print({ templateId: 'pos.receipt.retail', data: bill });
} catch (error) {
  switch (error.code) {
    case 'unreachable':      return offerInstall();      // app not running
    case 'not_paired':       return reconnect();         // token revoked
    case 'printer_offline':  return tellUserToCheckCable();
    case 'render_failed':    return reportBug(error.message);   // your payload
    default:                 throw error;
  }
}

Without the SDK

It is an ordinary JSON API. Anything that speaks HTTP can drive it.

terminal
# The bridge is a plain HTTP server. No SDK required.
curl http://127.0.0.1:47600/printol/v1/ping

curl -X POST http://127.0.0.1:47600/printol/v1/print \
  -H 'authorization: Bearer ptl_live_…' \
  -H 'content-type: application/json' \
  -d '{"template_id":"pos.receipt.retail","data":{ … }}'

Where to go next