How-to8 min read

HTML to PDF From Code: Which HTML and CSS Gets Printed

What HTML to PDF prints, tested: grid, flexbox, @page sizes, web fonts and repeated table headers work; JavaScript does not run. With curl examples.

By the PDF123 team Β· Updated 2026-11-05

HTML to PDF prints your HTML and CSS with WeasyPrint, not with a browser. In tests, grid, flexbox, @page size and margins, web fonts from a public address, repeated table headers and page numbers all came out; JavaScript did not run at all. Send one .html file, or a ZIP holding the HTML with its stylesheets, images and fonts. For a page that lives at a public address, URL to PDF does the same thing from the address.

Which HTML and CSS does an HTML to PDF converter actually print?

The call is one multipart request with the file as fileInput:

curl -X POST https://pdf123.xyz/api/v1/convert/html/pdf \
  -F [email protected] \
  -o page.pdf

Or from the command line, with the same result:

pdfx html-to-pdf page.html -o page.pdf

I ran both on 2026-10-23 against a local copy of the server, on a test page with @page { size: A5; margin: 12mm }. Each returned HTTP 200 (curl) or exit code 0 (pdfx), and pdfinfo reported 1 page of 419.528 x 595.276 points, which is A5.

The table below comes from a set of small test pages, each converted the same way and then checked with pdfinfo, pdftotext, pdffonts and pdfimages, plus a look at a rendered image where layout was the question. It reports what came out, not what a specification promises.

Feature What I sent What came out
Page size from CSS @page { size: A5; margin: 12mm } 419.528 x 595.276 pt (A5)
Custom page size @page { size: 210mm 99mm } 595.276 x 280.63 pt
No @page rule a bare HTML file 595.276 x 841.89 pt (A4)
CSS grid grid-template-columns: 1fr 2fr 1fr; gap: 10mm, six cells three columns in the ratio 1:2:1, cells 4 to 6 on the next row
Flexbox display: flex; justify-content: space-between, three items items spread from the left to the right edge of the line
Web font from a public address @font-face with a .woff file on a public CDN embedded in the PDF under the name given in @font-face
Font bundled in a ZIP @font-face with a relative url() to a file in the archive embedded in the PDF
Font installed on the machine font-family: "Times New Roman" embedded, on my test machine; this depends on the server's fonts
Font that exists nowhere font-family: "NoSuchFontAnywhere", monospace the generic monospace fell back to a font on the machine
Forced page break break-before: page and page-break-before: always 3 pages: the text before the first break, then one page per break
Table over several pages, header in <thead> a 100-row table, A5 4 pages, header row at the top of every page
Table over several pages, header as an ordinary row the same table without <thead> 4 pages, header row on page 1 only
Page numbers and running text @page { @bottom-center { content: "Page " counter(page) " of " counter(pages) } } Page 1 of 3, Page 2 of 3, Page 3 of 3 and a running header
@media print a print-only block and a screen-only block the print block printed, the screen block did not
Modern CSS custom properties, calc(), linear-gradient, border-radius, transform: rotate() all drawn
filter and position: sticky filter: blur(3px) and position: sticky the blur was ignored; the sticky block printed as an ordinary block
JavaScript a script setting textContent and calling document.write neither text appeared; the static text did
<noscript> text inside <noscript> printed
Image at a public address <img src="https://..."> embedded
Image at a private address an <img> whose address is a loopback address not loaded; the alt text printed; the call still returned 200

Two rows matter most when you generate HTML from code. A header row repeats on each page only if it sits in <thead>. And nothing a script writes into the page reaches the PDF, so a page that builds its content in the browser prints the shell it started with. A single-page app that sends an empty <div id="root"> produces an empty PDF.

How do I send HTML together with its stylesheets, images and fonts?

Put them in a ZIP. The converter uses index.html at the root of the archive if there is one, and otherwise the first file whose name ends in .html; a .htm file in the archive is not picked up. Relative paths resolve from the HTML file's own folder. A ZIP with index.html, css/style.css, img/logo.png and fonts/roboto.woff was converted with:

curl -X POST https://pdf123.xyz/api/v1/convert/html/pdf \
  -F [email protected] \
  -o c.pdf

The result was one page where the stylesheet applied, the bundled font was embedded under the name from @font-face, and the 300 x 120 image was present. In a second ZIP with no root index.html, site/page.html and site/logo.png, the converter used site/page.html and the image beside it loaded. A ZIP with no .html file at all returned HTTP 400, reason unsupported_file_type: The ZIP archive must contain an .html file. Markdown to PDF works differently: its image paths resolve from the archive root, as Convert Markdown to PDF Without Pandoc or LaTeX shows.

Files on your own computer are not reachable by a path. Anything the page links to has to be in the ZIP or at a public address. Resources at a public http or https address are fetched while the page renders. The fetcher allows up to 500 fetches per conversion, 64 MiB for each, and 15 seconds per request. Addresses on private or internal networks are blocked, which is why the image at a loopback address printed its alt text. Rendering is stopped after 180 seconds by default.

Fonts are the one place where results differ between machines. The Docker image of the service installs the DejaVu, Liberation, Noto and Noto CJK font packages. A font name in your CSS that the server does not have is replaced by a fallback, so a PDF that looks right on your machine can use another font on the server. Loading the font through @font-face, from the ZIP or from a public address, avoids the question.

Which renderer and version is behind it?

The converter is WeasyPrint, run from Python; there is no headless browser. The service's Docker image installs weasyprint==70.0 and stops the build if another version is on the path, and the PDFs from my test server carry WeasyPrint 70.0 in their Producer field (pdfinfo prints it). WeasyPrint's documentation says it is not based on a full rendering engine like WebKit or Gecko, and its CSS support page says the flexbox module "works for simple use cases but is not deeply tested" and the grid module "works for simple cases, but has some limitations". Test a complicated layout before you depend on it; the table above covers only the cases I tried.

What if the HTML is only available at an address?

URL to PDF takes urlInput, fetches the page itself and renders it with the same engine, so it does not run JavaScript either. A site that builds its content in the browser prints without that content.

curl -X POST https://pdf123.xyz/api/v1/convert/url/pdf \
  -F urlInput=https://example.com \
  -o output.pdf

That returned HTTP 200 and a 1-page A4 PDF whose Producer field read WeasyPrint 70.0; the download is always named output.pdf. Only public http and https addresses are accepted: a loopback address as urlInput returned HTTP 400 with reason set to url_not_allowed and the message Private, local and internal network addresses are not allowed. If the page needs a login or you need to control the exact bytes, save the HTML and send it to HTML to PDF instead.

Questions people ask

Does HTML to PDF run JavaScript?

No. In the test, a script that set an element's text and called document.write added nothing to the PDF. The engine reads HTML and CSS only, so send the HTML your script would have produced.

How do I set the paper size and margins?

With an @page rule in your own CSS, such as @page { size: A5; margin: 12mm }. The tool has no paper or margin fields and adds no default stylesheet, so a page without a rule came out as A4.

Why is the font different from the one in my browser?

A font name in CSS is looked up on the server, not on your machine, and a name the server lacks falls back to another font. Bundle the font in the ZIP or load it with @font-face from a public address, and it is embedded in the PDF.

Can it print a page that needs a login?

Not through URL to PDF, which sends no cookies. Save the HTML while you are logged in, put it in a ZIP with its assets, and use HTML to PDF.

Open tool
Process in the browser β€” no watermark, files removed after the job.
Open tool