Getting started

Quick start

Five steps to your first page: load the SDK, connect and authorise, pick a printer, print, and preview when the user should confirm.

Last updated

Prerequisite: PrintLink is installed and running, the console overview shows ws://127.0.0.1:17888/ws, and your page and the printer are on the same computer (the default case).

1. Load the SDK

PrintLink serves the browser build itself; one line in your page (global PrintLink):

<script src="http://127.0.0.1:17888/sdk/printlink.js"></script>

If you changed the port, replace 17888. The console overview gives you both addresses with a copy button.

2. Connect and authorise

const client = new PrintLink.PrintLinkClient({ appName: 'My business app' });

const hello = await client.connect();   // connect to the local PrintLink
console.log(hello.agentVersion, hello.platform);

await client.ensureAuthorized();        // authorised? returns. else starts pairing

The first time, PrintLink shows a confirmation on the user's computer: may this site print? Once accepted, the token is stored in the browser and the user is not asked again.

Four permissions are granted by default: read printers, read profiles, print, preview. Request more in the same call: ensureAuthorized(['job.read', 'job.cancel']).

3. Pick a printer

const { queues } = await client.listPrinters();
const queueId =
  queues.find((q) => q.isUserPreferred)?.queueId ??   // preferred in the console
  queues.find((q) => q.isSystemDefault)?.queueId ??   // OS default
  queues[0]?.queueId;

Each queue carries live status (status.state: ready / printing / paused / offline…). To find out which paper sizes or duplex modes it supports, call client.getCapabilities(queueId) — see printers & profiles.

4. Print

compose() is the chained form: add content in page order, set the paper once, submit once (the upload happens once):

const bytes = new Uint8Array(await (await fetch('/invoice.pdf')).arrayBuffer());

const job = await client
  .compose({ queueId })
  .paper('A4')
  .title('Invoice INV-2026-0001')
  .addPdf(bytes)
  .printAndWait();

console.log(job.status);   // 'completed' | 'failed' | 'canceled'
console.log(job.warnings); // e.g. duplex fell back to one-sided

5. Preview when the user should confirm

preview() takes exactly the same parameters as print(); the only difference is that it opens the preview window first. What the user sees is what the printer receives — confirming does not re-render.

const asset = await client.uploadAsset(bytes, 'application/pdf');
const session = await client.preview({
  queueId,
  source: { items: [{ type: 'pdf', asset }] },
});
console.log(session.pageCount, session.warnings);
// The user confirms in the window; your page can also trigger it:
// await client.previewPrint(session.previewSessionId);

Three states to handle in your UI

These three mean very different things — do not treat them all as "printing failed":

const probe = await PrintLink.detect();
if (!probe.installed) {
  // (1) Not installed / not running: guide the user, do not just fail
  showInstallGuide();
} else {
  try {
    await client.connect();
    await client.ensureAuthorized();
  } catch (err) {
    if (err instanceof PrintLink.PrintLinkError && err.code === 'PAIRING_PENDING') {
      // (2) Waiting for the user to accept on their computer
      showWaitingForApproval();
    } else {
      // (3) A real failure: branch on err.code
      showError(err.code, err.retryable);
    }
  }
}

Every failure is structured: code (stable machine code) + message + retryable. Always branch on code, never parse message. The full list is in error codes.

Complete example

Paste this into a blank HTML page (pick a PDF, then print silently):

<!doctype html>
<meta charset="utf-8" />
<input id="file" type="file" accept="application/pdf" />
<button id="print" disabled>Print</button>
<pre id="log"></pre>

<script src="http://127.0.0.1:17888/sdk/printlink.js"></script>
<script type="module">
  const log = (msg) => (document.querySelector('#log').textContent += msg + '\n');
  const client = new PrintLink.PrintLinkClient({ appName: 'PrintLink quick start' });

  const probe = await PrintLink.detect();
  if (!probe.installed) {
    log('PrintLink not detected — install and start it first.');
  } else {
    await client.connect();
    await client.ensureAuthorized();
    log(`Connected to PrintLink v${probe.version}`);
    document.querySelector('#print').disabled = false;
  }

  client.onJobStatus((event) => log(`job ${event.jobId}: ${event.status}`));

  document.querySelector('#print').onclick = async () => {
    const file = document.querySelector('#file').files[0];
    if (!file) return log('Pick a PDF first');
    const { queues } = await client.listPrinters();
    const queueId = (queues.find((q) => q.isSystemDefault) ?? queues[0]).queueId;
    const job = await client.compose({ queueId }).title(file.name).addPdf(file).printAndWait();
    log(`terminal: ${job.status}`);
  };

  window.addEventListener('pagehide', () => client.close());
</script>

Always close() when leaving

client.close() drops the connection and clears subscriptions. Call it when a React or Vue component unmounts too, otherwise hot reloads pile up connections.

Next steps

On this page