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 pairingThe 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-sided5. 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.