Most “image to PDF” tools ask you to upload files to a server.
That is fine for a meme screenshot. It is a bad idea for:
- ID photos
- contracts
- invoices
- school or visa paperwork
If the conversion can happen in the browser, the original images never need to leave the device. This post shows a practical approach using pdf-lib.
Why browser-side conversion?
Pros:
- Better privacy for sensitive documents
- Works offline after the page loads
- No waiting for upload/download round trips
- Easier to support “one image per page” layouts
Cons:
- Very large batches can stress low-end phones
- You depend on the browser’s image decoder (JPG/PNG/WEBP are usually fine)
For many personal and admin workflows, local conversion is the better default.
Core idea
- Read each image as bytes in the browser
- Embed it into a PDF page with
pdf-lib - Export a
Bloband trigger download
Install:
npm install pdf-lib
Minimal example: one image → one PDF page
import { PDFDocument } from 'pdf-lib'
async function imageFileToPdf(file) {
const bytes = new Uint8Array(await file.arrayBuffer())
const pdf = await PDFDocument.create()
let image
if (file.type === 'image/png') {
image = await pdf.embedPng(bytes)
} else {
// jpg/jpeg (and many cameras)
image = await pdf.embedJpg(bytes)
}
// Use the image's own size (in PDF points)
const page = pdf.addPage([image.width, image.height])
page.drawImage(image, {
x: 0,
y: 0,
width: image.width,
height: image.height,
})
const pdfBytes = await pdf.save()
return new Blob([pdfBytes], { type: 'application/pdf' })
}
This already covers a common case: turn a photo into a single-page PDF.
Better UX: multiple images, one image per page
Paperwork almost always needs page order.
async function imagesToPdf(files) {
const pdf = await PDFDocument.create()
for (const file of files) {
const bytes = new Uint8Array(await file.arrayBuffer())
let image
if (file.type === 'image/png') {
image = await pdf.embedPng(bytes)
} else if (file.type === 'image/jpeg') {
image = await pdf.embedJpg(bytes)
} else {
// For webp/gif/bmp, draw to canvas first, then export PNG/JPEG bytes
continue
}
const page = pdf.addPage([image.width, image.height])
page.drawImage(image, {
x: 0,
y: 0,
width: image.width,
height: image.height,
})
}
return new Blob([await pdf.save()], { type: 'application/pdf' })
}
Tip: let users drag to reorder before conversion. Page order matters more than fancy settings.
Optional: fit images onto A4
If the destination is a government form or printer, A4 is often expected.
const A4 = { width: 595.28, height: 841.89 } // points
function fitContain(imgW, imgH, boxW, boxH, margin = 24) {
const maxW = boxW - margin * 2
const maxH = boxH - margin * 2
const scale = Math.min(maxW / imgW, maxH / imgH)
const width = imgW * scale
const height = imgH * scale
return {
width,
height,
x: (boxW - width) / 2,
y: (boxH - height) / 2,
}
}
Then draw with those coordinates on an A4 page instead of using the raw pixel size.
Handling WEBP (and other formats)
pdf-lib embeds JPG/PNG directly. For WEBP:
- Draw the image onto a
<canvas> - Export with
canvas.toBlob('image/jpeg')or'image/png' - Embed the result
That keeps everything client-side.
Product note
I work on OpenPicBox, a set of browser-based image tools. The image-to-PDF flow follows the same idea:
- mix JPG / PNG / WEBP
- reorder pages
- export one image per page
- choose A4 / Letter / original size
- conversion stays in the browser
Direct format landings:
If you are building your own tool, start with the snippets above. If you just need the workflow today, try the pages and keep the files on your device.
Top comments (0)