Built for the developer who already has the HTML
You have a Node service that renders an invoice template to HTML. Or a Rails app that builds a monthly statement. Or a static site generator producing release notes. The HTML is done. You need a PDF — and you do not want to spin up a Puppeteer container, manage Chromium binaries, or send the document to a third-party API to do it. This tool does the last mile: paste the rendered markup, click Generate, download the file. Nothing leaves the browser.
The narrow scope is deliberate. URL-to-PDF — point at example.com, get a PDF — is the obvious feature, and browser tools cannot honestly ship it. The same-origin policy stops JavaScript from reading another site's HTML, and the workaround (a server proxy) is exactly the upload step we refuse to add. If you have the markup, we render it. If you do not, the honest answer is to render it on the server you already control and paste the result here.
How it works
- Paste HTML and CSS into the editor. A starter invoice template is loaded by default — replace it with your own markup. The editor highlights nothing fancy; it is a textarea with line numbers and a monospace font, optimised for paste-and-go.
- Watch the live preview render. The right pane is a sandboxed iframe. As you type, the iframe re-renders. Sandbox flags allow same-origin and inline scripts but block top navigation, popups, and form submissions — the iframe cannot reach this page or any third-party origin.
- Pick page size and margin. A4, Letter, Legal, or A5. Margin presets at 5, 10, 15 mm or a custom value. The tool passes these directly to html2pdf.js.
- Click Generate PDF. html2pdf.js (a wrapper around html2canvas + jsPDF) rasterizes the iframe contents page by page and assembles a PDF. Download starts automatically.
Privacy — what stays where
The HTML you paste lives in the editor textarea — a JavaScript variable in this tab. The preview iframe renders that string as a DOM. html2pdf.js reads the iframe DOM, draws each page to an off-screen canvas, and writes the canvas into a PDF blob using jsPDF — all in browser memory. The blob is downloaded via a same-origin URL.createObjectURL link. At no point does the markup or the resulting PDF leave the browser.
Compare this to a typical online HTML-to-PDF converter: you paste your invoice template (often containing real customer data — names, addresses, line items) into a form, the form submits to a server, the server renders the PDF and emails it back or returns a download URL. That entire round trip is a leak surface. For an invoice with a customer's home address, a paystub with a salary figure, or a contract with a counterparty's identity, the leak matters. Browser-local rendering removes it.
External resources are a separate concern. If your HTML embeds <img src="https://example.com/logo.png">, that image is fetched from example.com when the iframe renders — that fetch is visible to your network. For full air-gap behaviour, embed images as base64 data URIs in the HTML you paste. Same applies to web fonts: inline them via @font-face with base64-encoded TTF, or use a system font stack.
When to use this — and when not to
Use this for: invoice generation from your own templates, monthly statements, custom receipts, simple reports, takehome assessments, exported documentation pages, screenshot-substitute PDFs for visual records, internal tooling where the source HTML is already available.
Skip this for: capturing live websites you do not control (use Puppeteer on a server, or a browser extension that has the necessary permissions), high-volume programmatic generation (use jsPDF or pdfkit directly in your backend), forms with selectable text fields (the canvas-based rendering produces raster PDFs — text is an image, not selectable). For pixel-perfect print output with @page rules and CSS-paged-media features, server-side Chromium is still the right tool.
For PDF post-processing — merging multiple HTML-to-PDF outputs, adding page numbers, stripping metadata before sharing, or signing — chain this with the other tools below. Everything stays browser-local.
Frequently Asked Questions
Why does this tool take HTML as a string and not a URL?
Because the browser refuses to fetch most URLs from another origin. The same-origin policy and CORS rules block JavaScript from reading the rendered HTML of, say, example.com when the tool is running on pdfmavericks.com. A URL-to-PDF service that works has to fetch the page on a server, which means uploading your context — the URL, your IP, sometimes cookies — to that server. We do not do that. The honest browser-local model is: paste the markup you already have, and we render it locally. For dynamic invoice generation, server-side templating into HTML is the upstream step; this tool handles the HTML-to-PDF step downstream without sending the document anywhere.
What does CORS mean here, and why does it block URL-to-PDF?
CORS — Cross-Origin Resource Sharing — is the browser's rule that scripts on one origin (pdfmavericks.com) cannot read responses from another origin (example.com) unless that other origin explicitly opts in via a header. Almost no public website opts in. So a JavaScript fetch of a third-party URL either succeeds with no body (opaque response) or fails outright. There is no workaround that does not involve a server proxy — and a server proxy is exactly the upload step we refuse to add. If you control the source HTML, paste it. If you do not, the right tool is a headless-Chrome service running on a server you trust, not a browser-local tool pretending to be one.
Will my web fonts and external images render correctly?
Inline content always renders. Web fonts loaded via @import or @font-face from a third-party CDN often do not — html2canvas (the library inside html2pdf.js) draws the page to a canvas, and at draw time the fonts must already be loaded by the browser. Cross-origin font requests fail under the same CORS rules. The reliable pattern is to embed fonts as base64 data URIs in your CSS, or to use system font stacks (system-ui, -apple-system, Segoe UI, Roboto). External images via <img src="https://..."> work only when the host sends Access-Control-Allow-Origin and the request is made with crossorigin="anonymous". For most invoices and reports, base64-embedded images and system fonts are the safe default.
What page sizes and margins are supported?
Page sizes: A4 (210 × 297 mm), Letter (8.5 × 11 in), Legal (8.5 × 14 in), and A5 (148 × 210 mm). Margins are uniform on all four sides — pick from 5, 10, 15 mm presets, or enter a custom value in millimetres. Orientation is portrait by default; html2pdf.js can do landscape but the typical developer use case (invoices, statements) is portrait. If you need landscape, the workaround is to set CSS @page { size: A4 landscape } in your HTML and let html2pdf.js infer from the layout — the result is acceptable for most reports.
How does this compare to a server-side renderer like Puppeteer?
Puppeteer or Playwright running headless Chromium on a server produces print-quality PDFs with full CSS support, including @page rules, page-break-inside, and modern CSS features like grid and flex with no quirks. The trade-off is that the document goes to the server. For internal tooling where the server is yours, that is fine. For a public free tool, it is not — every PDF a user generates is a leak vector. html2pdf.js renders via canvas, which is rasterized: text in the PDF is an image, not selectable text by default. The library has a 'jsPDF' html mode that produces text PDFs, but layout fidelity drops. We default to the canvas mode for visual fidelity; the trade-off is non-selectable text in the output. State this in your client expectations.
Is anything sent to a server?
No. The HTML you paste, the rendered preview iframe, and the PDF that html2pdf.js generates all live inside this tab. There is no upload step, no temporary copy, no analytics on the markup content. The script html2pdf.js itself loads from this site's bundle (not a CDN), so first-paint also does not phone home. Verify in the browser DevTools Network tab — after the page loads, generating a PDF triggers no further network requests beyond what your own HTML references (e.g., if you embed an external image URL).
Can I include JavaScript in the HTML I paste?
Yes, but it runs inside the sandboxed preview iframe and cannot reach the parent page or make network calls to other origins. Use it for layout helpers — formatting numbers, computing totals, populating a table from inline data. Do not use it to fetch data from an API at render time; the iframe sandbox blocks third-party fetches and the canvas is drawn before async work would complete. The reliable pattern: build your HTML with all data already inlined, server-side or in your own app, then paste the finished markup here.
What is the maximum HTML size?
Practical limit is around 5 MB of HTML or roughly 200 pages of rendered output. Beyond that, html2canvas slows substantially and may run out of memory on a single tab. For very long documents, split into chapters and merge the resulting PDFs with /merge. The bottleneck is the canvas rasterization step, not the PDF assembly — html2pdf.js draws each page to a full-resolution canvas at 2× device pixel ratio, which is memory-heavy on long pages. For a 50-page invoice batch, expect a few seconds per page on a modern laptop.
Does the output PDF have selectable text?
Not in the default mode. html2pdf.js uses html2canvas to rasterize each page to a PNG, then drops that image into a jsPDF page. The result looks identical to the rendered HTML but text is part of the image — it cannot be copy-pasted or searched. For most invoice and report use cases this is acceptable (the recipient prints it or attaches it to an email). If you need selectable text, a server-side renderer like Puppeteer is the better choice, with the privacy trade-off that implies. Some users add a hidden text layer in the PDF via post-processing; that is out of scope here.