Printlet

Open your own web app inside Printlet and print receipts, tickets and labels on a Bluetooth thermal printer. Your web app only has to send one message; Printlet handles pairing, paper width and printing.

ESC/POS Bluetooth printers 58 mm · 384 dots 80 mm · 576 dots Any http / https site

How it works

Printlet is a lightweight browser with a built-in Bluetooth printer connection. You choose which website it opens, and that website can ask Printlet to print an image. There is no SDK to install and no server in between: the image goes from your page straight to the printer.

01

Open your web app

Add your site's address in Printlet settings and make it the default page.

02

Connect a printer

Scan for your Bluetooth thermal printer and tap it. Printlet reconnects automatically next time.

03

Print from the page

Your page renders the receipt as an image and posts it to Printlet, which prints it and cuts the paper.

Set up the app

Everything is configured from the floating settings button, which you can drag to any screen edge.

  1. Add your website. Enter an https:// or http:// address. Domains, localhost and local IP addresses (for example http://192.168.1.20:3000) are all accepted, which makes testing on your local network easy.
  2. Set it as default so Printlet opens it on launch. You can keep several sites and switch between them.
  3. Choose the paper width: 58 mm or 80 mm, matching the roll in your printer.
  4. Connect the printer. Turn the printer on, tap Scan, then tap your printer in the list. Use Test print to confirm it works.
  5. Optional: pick a theme colour for the settings button and panel.

On Android, pull down at the top of the page to reload it, and the back button goes back through your site's history.

Integrate your web app

Inside Printlet, your page has a window.ReactNativeWebView object. Send it a JSON string containing your receipt as an image data URL:

if (window.ReactNativeWebView) {
  window.ReactNativeWebView.postMessage(
    JSON.stringify({
      data_img: dataUrl, // "data:image/png;base64,..."
    })
  );
}

That is the whole integration. A typical print function looks like this:

const inPrintlet = () => Boolean(window.ReactNativeWebView);

function printImage(dataUrl) {
  if (inPrintlet()) {
    window.ReactNativeWebView.postMessage(JSON.stringify({ data_img: dataUrl }));
    return;
  }
  // Regular browser: fall back to the system print dialog.
  const w = window.open('');
  w.document.write(`<img src="${dataUrl}" style="width:100%" onload="print()">`);
}

You can also give users a Printer settings button in your own UI that opens Printlet's settings panel:

window.ReactNativeWebView?.postMessage(
  JSON.stringify({ action: 'open-printer-setting' })
);

If you use TypeScript, declare the bridge once:

declare global {
  interface Window {
    ReactNativeWebView?: { postMessage(message: string): void };
  }
}

Messaging is one-way. Printlet does not send a result back to the page; it shows its own "Printing report..." or "Please connect bluetooth printer." notice. Only show your own success message if you need to, and don't wait for a reply.

Message reference

Every message is a single string produced by JSON.stringify.

Print an image

FieldTypeDescription
data_img string, required The image as a data URL: data:image/png;base64,… or data:image/jpeg;base64,….
printerWidth number, optional Print width in printer dots. Leave it out to use the paper width chosen in Printlet settings (recommended). Use 384 for 58 mm or 576 for 80 mm.
fit boolean, optional (default true) When true, blank columns on the left and right of the image are cropped so the content fills the paper width. Send false to keep your image's side margins exactly as designed.

Open printer settings

FieldValueDescription
action "open-printer-setting" or "open-settings" Opens Printlet's settings panel so the user can connect a printer or change the paper width.

Send only these two message shapes. Any other JSON is treated as a print request and will show a printer error.

Image guidelines

Printlet scales every image to the full print width (keeping its proportions) and converts it to pure black and white. Designing for that from the start gives sharp, readable prints.

Size

PaperPrint widthRender your image atDesign width in CSS
58 mm384 dots (≈48 mm printable)384 px, or 768 px at 2×58 mm or 384 px
80 mm576 dots (≈72 mm printable)576 px, or 1152 px at 2×80 mm or 576 px
  • Width: render at the print width or up to 2× it. Larger images are scaled down smoothly; much smaller ones are scaled up and look blurry.
  • Height: any height works and is kept in proportion. Keep white space at the top and bottom small; Printlet already feeds a little paper and cuts after each image.
  • Very long documents: keep a single image under about 5,000 px tall. Split longer output into several messages; each one is printed and cut separately.

Contrast and fonts

  • Black on white only. Colours and greys are converted to black or white, so light grey text may disappear and coloured backgrounds print as solid blocks.
  • Transparent areas print white, so a transparent PNG is fine.
  • Use sturdy fonts. Aim for at least 12 px text at 1× on 58 mm paper (14 px on 80 mm) and prefer regular or bold weights; hairline fonts break up.
  • Avoid photos and gradients; they print as coarse patterns. Logos work best as simple black artwork.

Format and payload

  • Use PNG. It keeps text edges crisp and is small for mostly-white receipts. JPEG compression adds specks around text.
  • Keep the data URL under about 1 MB. A typical receipt rendered at 2× is 50–150 KB.

Margins and fit

By default (fit: true) Printlet crops empty space on the left and right before scaling, so your content always spans the paper. That means side padding in your image is removed. If you want printed side margins, either send fit: false with margins included in the image, or keep fit on and design the receipt edge to edge.

Reference implementation: Pocket POS

Pocket POS is a free, browser-based point of sale built as a working example of a Printlet integration. Add https://imthejuan.info/pos/ in Printlet, complete a sale and press Thermal Print.

Here is what it does, and what you can copy:

  1. Designs the receipt in HTML at paper size. The receipt is laid out at 58 mm or 80 mm wide in CSS, in black on white.
  2. Renders it to PNG in the browser with the open-source html-to-image library, at pixelRatio: 2 with a white background, after waiting for images and web fonts to load.
  3. Posts the PNG to Printlet with the message above, and uses the browser's print dialog when it isn't running inside Printlet.
  4. Prevents double prints by disabling the button while the image is being made.

Its Receipt Designer also shows the guidelines in action: pick the paper width, turn on Bold text for darker thermal prints, and adjust or remove the receipt padding.

async function thermalPrint(receiptEl) {
  await document.fonts.ready;
  const dataUrl = await htmlToImage.toPng(receiptEl, {
    pixelRatio: 2,
    backgroundColor: '#ffffff',
  });
  if (window.ReactNativeWebView) {
    window.ReactNativeWebView.postMessage(JSON.stringify({ data_img: dataUrl }));
  } else {
    // print the image with window.print() instead
  }
}

Complete example

A single HTML file you can host anywhere and open in Printlet:

<!DOCTYPE html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    #receipt {
      width: 384px;              /* 58 mm paper; use 576px for 80 mm */
      padding: 12px 0;
      background: #fff;
      color: #000;
      font: 600 16px/1.35 'Courier New', monospace;
    }
    .row { display: flex; justify-content: space-between; }
    .center { text-align: center; }
  </style>
</head>
<body>
  <div id="receipt">
    <div class="center"><strong>MY STORE</strong><br>123 Main Street</div>
    <hr>
    <div class="row"><span>2 × Coffee</span><span>₱190.00</span></div>
    <div class="row"><span>1 × Bread</span><span>₱65.00</span></div>
    <hr>
    <div class="row"><strong>TOTAL</strong><strong>₱255.00</strong></div>
    <p class="center">Thank you!</p>
  </div>

  <button id="print">Print</button>
  <button id="printer">Printer settings</button>

  <script src="https://unpkg.com/html-to-image@1.11.13/dist/html-to-image.js"></script>
  <script>
    const send = (msg) => window.ReactNativeWebView?.postMessage(JSON.stringify(msg));

    document.getElementById('print').addEventListener('click', async (e) => {
      e.target.disabled = true;
      try {
        const dataUrl = await htmlToImage.toPng(document.getElementById('receipt'), {
          pixelRatio: 2,
          backgroundColor: '#ffffff',
        });
        if (window.ReactNativeWebView) send({ data_img: dataUrl });
        else window.open('').document.write(`<img src="${dataUrl}" onload="print()">`);
      } finally {
        e.target.disabled = false;
      }
    });

    document.getElementById('printer').addEventListener('click', () =>
      send({ action: 'open-printer-setting' })
    );
  </script>
</body>
</html>

Already drawing on a <canvas>? Send canvas.toDataURL('image/png') as data_img; no library is needed.

Troubleshooting

ProblemWhat to check
"Please connect bluetooth printer." Open Printlet settings, make sure Bluetooth is on and the printer is selected, then try Test print. Printers that are off or connected to another phone won't respond.
Printer not found when scanning Turn the printer on and keep it close. Allow Printlet's Bluetooth (and on older Android versions, location) permission in the phone's settings. Pairing the printer in the phone's Bluetooth settings first can help.
Nothing happens when printing Check that window.ReactNativeWebView exists (you are inside Printlet) and that you send a string made with JSON.stringify, not an object.
Print is faint or broken up Use pure black text, bold or larger fonts, and no grey. Render at the print width or 2×.
Text is tiny Your image is wider than a receipt (for example a full web page). Render only the receipt element, sized to the paper width.
Side margins disappear That's fit cropping blank space. Send fit: false to keep them.
Receipt is cut off on the right or too narrow The paper width in Printlet doesn't match the roll, or printerWidth is set to the wrong value. Remove printerWidth and pick the right paper in settings.

Privacy

Printlet has no accounts and sends nothing to us. Its settings stay on your device, and print images go directly from your web page to your printer over Bluetooth. The app shows Google AdMob banner ads. Read the full Printlet Privacy Policy.

Questions or integration help

Email: wanmigs@gmail.com