Shipping label PDF from HTML (4×6 in)
Shipping label PDFs from HTML at 4×6 inches, with zero margins, Code 128 barcodes (JsBarcode or server SVG) and many labels per file.
The 4×6 inch label is the standard size for most direct thermal label printers in warehouses and e-commerce. If you make labels yourself (for picking, returns, internal routing, or to lay out a tracking number you already have), HTML is a quick way to design them. A PDF is what label printers and print dialogs handle best.
When you buy postage from a carrier, the carrier's API usually returns the official label, often as a PDF or ZPL. Use your own HTML label for everything around it, or where the carrier lets you print your own, and follow the carrier's barcode and layout specification.
Quick answer. Send your label HTML with a 4×6 inch page size and zero margins. Each label on its own page costs one credit.
curl https://html-pdf.com/v1/pdf -H "Authorization: Bearer $HTML_PDF_KEY" \
-d '{"html": "<div>SHIP TO: Jane Doe</div>", "width": "4in", "height": "6in", "margin": "0"}' -o label.pdf
Set the page to exactly 4×6 inches
This section shows the two ways to get an exact label size. Both give the same result.
- API options:
"width": "4in", "height": "6in", "margin": "0". - CSS:
@page { size: 4in 6in; margin: 0; }plus"prefer_css_page_size": true.
Use zero margins and add spacing with padding inside the label, so nothing gets clipped or scaled. Thermal printers print black only, so use pure black text and bars on a white background.
The label template
This template gives you a ready label with sender, recipient and a barcode slot.
<!doctype html><html><head><meta charset="utf-8"><style>
@page { size: 4in 6in; margin: 0; }
html, body { margin: 0; }
body { font: 12px/1.3 Arial, Helvetica, sans-serif; color: #000; }
.label { box-sizing: border-box; width: 4in; height: 6in; padding: 0.15in;
border: 2px solid #000; display: flex; flex-direction: column; break-after: page; overflow: hidden; }
.label:last-child { break-after: auto; }
.from { font-size: 10px; border-bottom: 2px solid #000; padding-bottom: 6px; }
.to { flex: 1; font-size: 16px; padding-top: 10px; }
.to strong { font-size: 20px; }
.barcode { text-align: center; border-top: 2px solid #000; padding-top: 8px; }
.barcode svg { width: 100%; height: 1.3in; }
</style></head><body>
<div class="label">
<div class="from">FROM: Northwind Store · 12 Dock Rd · Leeds LS1 4AP · UK</div>
<div class="to">SHIP TO:<br><strong>Jane Doe</strong><br>221B Baker Street<br>London NW1 6XE<br>United Kingdom</div>
<div class="barcode"><svg class="bc" data-value="NW-000123"></svg></div>
</div>
</body></html>
break-after: page on each .label puts every label on its own page, so one request can produce a whole batch. Fixed width/height with overflow: hidden makes sure a long address never pushes content onto an extra page.
Option A: barcodes in the page with JsBarcode
This option draws barcodes in the browser while the page renders. JsBarcode draws Code 128, EAN and other 1D barcodes into SVG. Load it, render every barcode, then set a flag that the API waits for with wait_for_expression.
<script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.6/dist/JsBarcode.all.min.js"></script>
<script>
document.querySelectorAll("svg.bc").forEach(el =>
JsBarcode(el, el.dataset.value, { format: "CODE128", displayValue: true, margin: 0, width: 2, height: 80 }));
window.labelsReady = true;
</script>
Then render it from Node.js.
import fs from "node:fs";
const html = fs.readFileSync("labels.html", "utf8");
const res = await fetch("https://html-pdf.com/v1/pdf", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.HTML_PDF_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
html,
width: "4in", height: "6in", margin: "0",
wait_for_expression: "window.labelsReady === true",
filename: "labels-2026-10-10.pdf",
}),
});
if (!res.ok) throw new Error((await res.json()).error.message);
fs.writeFileSync("labels.pdf", Buffer.from(await res.arrayBuffer()));
Option B: generate the barcode SVG on your server
This option builds the barcode before you send the HTML, so there is no CDN or page JavaScript involved. In Python, python-barcode can write SVG.
import os, html, io, requests
import barcode
from barcode.writer import SVGWriter
def code128_svg(value):
buf = io.BytesIO()
barcode.get("code128", value, writer=SVGWriter()).write(buf, options={"module_height": 18, "quiet_zone": 2})
svg = buf.getvalue().decode()
return svg[svg.index("<svg"):] # drop the XML declaration
orders = [
{"id": "NW-000123", "name": "Jane Doe", "addr": "221B Baker Street<br>London NW1 6XE"},
{"id": "NW-000124", "name": "Sam Lee", "addr": "4 Canal St<br>Manchester M1 3HE"},
]
labels = "".join(
f'<div class="label"><div class="to">SHIP TO:<br><strong>{html.escape(o["name"])}</strong><br>{o["addr"]}</div>'
f'<div class="barcode">{code128_svg(o["id"])}</div></div>'
for o in orders) # escape every field in real code; addr here is trusted sample data
css = open("label.css").read() # the styles from the template above
page = f'<!doctype html><html><head><meta charset="utf-8"><style>{css}</style></head><body>{labels}</body></html>'
r = requests.post("https://html-pdf.com/v1/pdf",
headers={"Authorization": f"Bearer {os.environ['HTML_PDF_KEY']}"},
json={"html": page, "width": "4in", "height": "6in", "margin": "0"}, timeout=60)
r.raise_for_status()
open("labels.pdf", "wb").write(r.content)
Each label is one page, so a 50-label batch costs 50 credits. For very large batches, split them so each request stays within the 20 MB request size and the 30-second render limit. In Node.js, bwip-js offers the same server-side SVG approach.
Printing tips
These tips help your labels print at the right size and scan on the first try.
- Print at actual size. In the print dialog choose "Actual size" or 100% scale, not "Fit to page". Otherwise the barcode shrinks and may not scan.
- Set the printer's media to 4×6 in (sometimes listed as 101.6 × 152.4 mm) in its driver settings.
- Test-scan a printed label with your warehouse scanner before running a large batch.
- Leave a quiet zone (blank space) on both sides of the barcode, because scanners need it.
- Pure black on white. Avoid gray text and colored backgrounds on thermal printers.
Related: page breaks for controlling where each label starts.
Make labels with html-pdf.com
This is how to get started with the hosted API. html-pdf.com renders HTML or URLs to PDF with Chromium and supports custom page sizes like 4×6 in with zero margins.
You get 100 free credits once you confirm your email, then pay from $0.005 per page, and purchased credits never expire. Your HTML is not stored. Get an API key or read the docs.
With the MCP server, an AI agent can produce a batch from an order list.
"Here are today's 12 orders as CSV. Make a 4×6 inch shipping label PDF with one label per page: sender address at the top, recipient in large text, and a Code 128 barcode of the order ID at the bottom."
FAQ
How do I make a 4x6 shipping label PDF from HTML?
Set the page to "width": "4in", "height": "6in", "margin": "0" in the API request, or use @page { size: 4in 6in; margin: 0; } with prefer_css_page_size. Give each label break-after: page to put many labels in one file.
How do I add a barcode to an HTML shipping label?
Draw it in the page with JsBarcode and wait for a ready flag with wait_for_expression, or generate the SVG on your server with python-barcode or bwip-js. Both produce Code 128 barcodes.
Why does my printed label barcode not scan?
The usual cause is printing with "Fit to page", which shrinks the barcode. Print at actual size (100%), leave a quiet zone on both sides, and use pure black on white.
How much does it cost to generate shipping labels?
One credit equals one PDF page, so each 4×6 label costs one credit. You get 100 free credits without a card, then packs run from $10 for 1,000 to $250 for 50,000 credits.
