
Security News
GPT-6 Astra Attempts Supply Chain Attacks Against Open Source Maintainers in Testing
GPT-6 Astra hits 100% on ExploitBench and finds zero-days autonomously, while independent tests reveal scope violations and monitoring gaps.
boxpdf-html
Advanced tools
Readable HTML-to-PDF rendering built on boxpdf. It is for invoices, receipts, reports, emails, and other authored document HTML where a useful static PDF matters more than browser pixel emulation.
npm install boxpdf-html boxpdf pdf-lib
Render an HTML file directly:
npx boxpdf-html invoice.html invoice.pdf
With generated Tailwind CSS:
npx tailwindcss -i ./tailwind.css -o ./dist/tailwind.css --minify
npx boxpdf-html invoice.html invoice.pdf --css ./dist/tailwind.css
With custom fonts and local images:
npx boxpdf-html invoice.html invoice.pdf \
--font ./Inter-Regular.ttf \
--bold-font ./Inter-Bold.ttf \
--font-family 'Inter=normal:Inter-Regular.ttf,bold:Inter-Bold.ttf'
Useful flags:
boxpdf-html <input.html> <output.pdf>
boxpdf-html - <output.pdf> # read HTML from stdin
boxpdf-html input.html output.pdf --css app.css
boxpdf-html input.html output.pdf --base-url ./public
boxpdf-html input.html output.pdf --debug
boxpdf-html input.html output.pdf --unsupported-css
boxpdf-html input.html output.pdf --profile
The CLI defaults to pdf-lib's built-in Helvetica family. Use real embedded fonts for production output when brand matching, unicode coverage, or exact metrics matter.
htmlToBoxpdf turns HTML into normal boxpdf nodes. You render those nodes with renderFlow.
import { readFile } from "node:fs/promises";
import { PDFDocument } from "pdf-lib";
import { loadFont, loadImage, renderFlow } from "boxpdf";
import { fontFamily, htmlToBoxpdf } from "boxpdf-html";
const html = await readFile("invoice.html", "utf8");
const pdf = await PDFDocument.create();
const inter = await loadFont(pdf, await readFile("Inter-Regular.ttf"));
const interBold = await loadFont(pdf, await readFile("Inter-Bold.ttf"));
const logo = await loadImage(pdf, await readFile("logo.png"));
const result = htmlToBoxpdf(html, {
font: inter,
boldFont: interBold,
resolveFont: fontFamily({
Inter: {
normal: inter,
bold: interBold
},
"sans-serif": {
normal: inter,
bold: interBold
}
}),
resolveImage: ({ url }) => (url === "logo.png" ? logo : undefined),
baseUrl: process.cwd(),
width: 532
});
await renderFlow(pdf, result.nodes, { margin: 40 });
const bytes = await pdf.save();
width is the CSS containing block width in PDF points. A US Letter page with 40pt margins has a 532pt content width, so width: 532 is a good default.
Fonts are explicit. boxpdf-html does not discover system fonts and does not ship a browser font stack. This keeps rendering deterministic and works in serverless runtimes.
At minimum, pass font. Pass boldFont and italicFont if your HTML uses bold or italic text:
const result = htmlToBoxpdf(html, {
font,
boldFont,
italicFont,
width: 532
});
For CSS font-family, use fontFamily():
const resolveFont = fontFamily({
Inter: {
normal: interRegular,
bold: interBold,
italic: interItalic,
boldItalic: interBoldItalic
},
Helvetica: {
normal: fallback,
bold: fallbackBold
},
"sans-serif": {
normal: fallback,
bold: fallbackBold
}
});
The resolver receives { families, weight, style } and returns a pdf-lib PDFFont. You can provide your own resolver when you need looser mapping, font aliases, language-specific fallbacks, or weight synthesis.
Gotchas:
font-family: system-ui only works if your resolver maps system-ui.Tailwind works when you render its generated CSS, not raw class names alone. The usual flow is:
boxpdf-html.Example source:
<div class="p-6 bg-[#f8fafc] text-gray-900">
<div class="max-w-[520px] rounded-[10px] border bg-white p-5 shadow-sm">
<div class="grid grid-cols-[1fr_2fr] gap-x-4 gap-y-3">
<div class="rounded-md border border-blue-200 bg-blue-50 p-3">
<p class="text-xs font-semibold uppercase tracking-wide text-blue-700">Status</p>
<p class="mt-1 text-sm font-bold">Paid</p>
</div>
<div class="rounded-md border border-gray-200 p-3">
<p class="text-xs font-semibold uppercase tracking-wide text-gray-600">Notes</p>
<p class="mt-1 text-sm leading-5">Two fraction column wraps later.</p>
</div>
</div>
</div>
</div>
Build CSS:
@import "tailwindcss";
@source "./invoice.html";
npx tailwindcss -i ./tailwind-input.css -o ./tailwind-output.css --minify
npx boxpdf-html invoice.html invoice.pdf --css ./tailwind-output.css
Supported Tailwind patterns include common spacing, color, text, border, radius, width/height, flex, grid, table, image, and arbitrary-value utilities. Unsupported utility declarations can be reported with --unsupported-css or diagnostics: { unsupportedCss: true }.
Tailwind gotchas:
shadow-*, transforms, filters, transitions, and browser-only effects are either ignored or reported as unsupported. The PDF should remain readable.The API uses resolveImage because pdf-lib images must be embedded before rendering:
const images = new Map([
["logo.png", await loadImage(pdf, await readFile("logo.png"))]
]);
htmlToBoxpdf(html, {
font,
resolveImage: ({ url }) => images.get(url),
baseUrl: process.cwd()
});
The CLI preloads local, http(s), and data: image URLs referenced by <img src> and CSS url(...). Missing images preserve their layout box when width/height can be inferred.
Supported:
parse5.css-tree.!important, inheritance, custom properties, var(), and common calc().Not a browser:
const result = htmlToBoxpdf(html, {
font,
width: 532,
diagnostics: { unsupportedCss: true, sampleLimit: 3 },
profile: (event) => console.log(event.phase, event.elapsedMs)
});
console.log(result.diagnostics?.unsupportedCss);
Unsupported CSS diagnostics are aggregated by property/value pair and include selector samples. Profile events cover parsing, CSS, style computation, render-tree construction, and output node counts.
First publish is manual, because npm needs the package to exist before trusted publishing can be attached:
pnpm install --frozen-lockfile
pnpm run typecheck
pnpm run test
pnpm run build
pnpm run pack:release
cd .pack
npm publish --access public
Then configure npm trusted publishing for future releases:
npm trust github boxpdf-html --repo earonesty/boxpdf-html --file release.yml
If your npm CLI does not support that command, configure it in npmjs.com package settings:
earonestyboxpdf-htmlrelease.ymlTrusted publishing currently requires npm 11.5.1+ and Node 22.14+; the npm trust CLI command itself requires npm 11.10+. The release workflow uses Node 24 and upgrades npm before publishing. Future releases are tag-driven:
git tag v1.0.0
git push origin v1.0.0
The workflow runs typecheck, tests, build, publishes with OIDC/provenance, and creates a GitHub Release with generated notes.
During local development, package.json depends on the adjacent checkout:
"boxpdf": "file:.."
Release packing is done through scripts/prepare-publish.mjs, which stages the package and rewrites the published manifest to a real semver dependency:
"boxpdf": "^1.7.0"
The script fails if a packed or published manifest would contain a local file: dependency.
Useful commands:
pnpm run typecheck
pnpm run test
pnpm run build
pnpm run tailwind:fixture
pnpm run visual:check
pnpm run pack:release
BOXPDF_DEP_VERSION=^1.7.0 pnpm run publish:release
FAQs
Readable HTML-to-PDF translator built on boxpdf.
The npm package boxpdf-html receives a total of 41 weekly downloads. As such, boxpdf-html popularity was classified as not popular.
We found that boxpdf-html demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
GPT-6 Astra hits 100% on ExploitBench and finds zero-days autonomously, while independent tests reveal scope violations and monitoring gaps.

Product
Socket can now send alerts and supply chain attack notifications to Microsoft Teams, with filters that route the right updates to each channel.

Security News
pnpm 12 rewrites the package manager in Rust, cutting install times by up to 90% while preserving pnpm 11 workflows and lockfiles.