How to Convert a Local HTML File to PDF
Convert a local HTML file to PDF with browser printing, Chrome headless, Puppeteer, or wkhtmltopdf, including print CSS and troubleshooting.
Fastest answer: open the local .html file in Chrome or Edge, press Ctrl/Cmd+P, choose Save as PDF, set the paper size, margins, orientation, and background graphics, then save. For repeatable jobs, use Chrome headless or Puppeteer. Use file:///absolute/path/to/file.html when a converter expects a URL.
1. Convert one local HTML file with a browser
- Open the file in Chrome, Edge, or another Chromium browser. You can double-click it, or enter a URL such as
file:///Users/alex/site/report.html. - Open the print dialog with Ctrl+P on Windows/Linux or Cmd+P on macOS.
- Set Destination to Save as PDF (or select your system PDF printer).
- Choose paper size, portrait or landscape orientation, margins, scale, and whether to print background graphics.
- Save the PDF, then inspect every page for clipped content, missing images, incorrect fonts, and awkward page breaks.
This is the best option for a one-off conversion because it needs no installation and lets you see the rendered page before saving.
2. Prepare the HTML for predictable print output
PDF conversion uses the document’s print layout. Add print-specific rules instead of relying only on the screen design:
<style>
@page {
size: A4;
margin: 16mm;
}
@media print {
.no-print,
nav,
.cookie-banner {
display: none !important;
}
h1, h2, h3 {
break-after: avoid;
}
table, figure, img {
break-inside: avoid;
}
a {
color: #000;
text-decoration: none;
}
}
</style>
Use relative paths that work from the HTML file. For example, assets/styles.css and images/chart.png should exist relative to the document. If a font, image, stylesheet, or script is remote, make sure the converter can reach it.
3. Convert with Chrome or Chromium headless
Chrome’s headless CLI can print a page directly to a PDF file. The official documentation describes the --headless --print-to-pdf approach: Chrome headless CLI documentation.
# Linux
google-chrome --headless --no-sandbox \
--print-to-pdf=/absolute/output/report.pdf \
file:///absolute/path/to/report.html
# macOS (adjust the Chrome path if needed)
/Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome \
--headless \
--print-to-pdf=/absolute/output/report.pdf \
file:///absolute/path/to/report.html
# Windows PowerShell
& "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe" \
--headless \
--print-to-pdf="C:\\output\\report.pdf" \
"file:///C:/site/report.html"
Use an absolute file:/// URL. Quote paths containing spaces. If the page builds content with JavaScript, a direct CLI print may happen before the content is ready; use Puppeteer when you need an explicit wait.
4. Automate conversion with Puppeteer (Node.js)
Puppeteer’s guide states: “For printing PDFs use Page.pdf().” Its PDF API generates output with the print CSS media type; call page.emulateMediaType('screen') when you deliberately want screen styling. See the Puppeteer PDF guide and Page.pdf API.
npm install puppeteer
// html-to-pdf.mjs
import puppeteer from 'puppeteer';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
const input = path.resolve(process.argv[2] ?? './report.html');
const output = path.resolve(process.argv[3] ?? './report.pdf');
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto(pathToFileURL(input).href, { waitUntil: 'networkidle0' });
// Uncomment this line when the PDF should use screen CSS instead of print CSS.
// await page.emulateMediaType('screen');
await page.pdf({
path: output,
format: 'A4',
printBackground: true,
margin: { top: '16mm', right: '16mm', bottom: '16mm', left: '16mm' },
preferCSSPageSize: true
});
} finally {
await browser.close();
}
console.log(`Wrote ${output}`);
node html-to-pdf.mjs ./report.html ./report.pdf
waitUntil: 'networkidle0' waits for network activity to settle, but it cannot know whether an application has finished a long asynchronous render. For that case, wait for a specific selector:
await page.goto(pathToFileURL(input).href, { waitUntil: 'domcontentloaded' });
await page.waitForSelector('#report-ready', { timeout: 30000 });
5. Use wkhtmltopdf for a standalone command-line workflow
wkhtmltopdf is an open-source LGPLv3 command-line tool that renders HTML into PDF using Qt WebKit. A basic conversion is:
wkhtmltopdf \
--page-size A4 \
--margin-top 16mm \
--margin-right 16mm \
--margin-bottom 16mm \
--margin-left 16mm \
file:///absolute/path/to/report.html \
/absolute/path/to/report.pdf
It can be useful when you want a single installed binary, but check your document against its rendering engine. Modern CSS and JavaScript may render differently than in current Chrome.
6. Important options and edge cases
| Requirement | What to do |
|---|---|
| Paper and orientation | Set @page { size: A4 landscape; } or the equivalent converter option. |
| Background colors and images | Enable “Background graphics” in browser printing or printBackground: true in Puppeteer. |
| Margins | Prefer @page for document-owned margins; otherwise set converter margins explicitly. |
| Screen versus print CSS | Puppeteer uses print media by default. Call emulateMediaType('screen') for screen rules. |
| Large tables | Use thead { display: table-header-group; } and avoid break-inside rules that force huge blank areas. |
| Images | Use accessible, readable paths and wait for image loading. Check natural dimensions and high-density assets. |
| Fonts | Install local fonts or wait for document.fonts.ready; verify that the PDF embeds or substitutes them acceptably. |
| JavaScript-generated content | Wait for a ready marker, a known API response, or an application-specific completion signal. |
| Local security restrictions | Keep assets beside the HTML or serve the directory locally if browser file access blocks them. |
| Page ranges | Print selected pages in the browser, or use a PDF post-processing tool after generation. |
7. Troubleshooting
Images or CSS are missing
Cause: relative paths resolve from a different working directory, or the file is not reachable from file:///. Fix: use paths relative to the HTML file, verify capitalization, and try an absolute file:/// URL or a local HTTP server.
The PDF is blank
Cause: JavaScript has not rendered, the input path is wrong, or the page requires a blocked resource. Fix: open the exact URL in a browser, wait for a ready element in Puppeteer, and inspect console and network errors.
Content is cut off
Cause: fixed widths, overflow rules, or a viewport wider than the paper. Fix: remove unnecessary fixed widths, add print rules, choose landscape, or reduce print scale.
Fonts look different
Cause: the font is unavailable, still loading, or replaced by a fallback. Fix: install or bundle the font, wait for document.fonts.ready, and confirm the selected font has the needed weights.
Page breaks split headings or cards
Cause: screen layout rules do not account for paged media. Fix: use break-after: avoid for headings and break-inside: avoid for small figures, cards, and tables. Avoid applying it to very large blocks.
Remote assets work in the browser but not in automation
Cause: authentication, certificate errors, CORS, network policy, or a resource that loads after the print starts. Fix: provide the required headers or cookies in automation, wait for completion, or copy required assets locally.
wkhtmltopdf output differs from Chrome
Cause: the tools use different rendering engines. Fix: choose one renderer for your production workflow and validate the exact document with it.
8. Performance, reliability, and cost
- One file: browser printing is fastest to set up.
- Many files: keep a browser process warm, reuse pages carefully, and limit concurrency so CPU and memory remain predictable.
- Reliability: pin the browser or converter version in CI, wait for fonts and application data, and archive representative PDFs for regression checks.
- Large documents: reduce oversized images, avoid unbounded canvas output, and split work when a single page consumes excessive memory.
- Cost: local Chrome, Puppeteer, and wkhtmltopdf have no per-document API charge; you pay in installation, compute, and maintenance.
9. Or skip the browser setup
If the HTML is available at a reachable URL, ScreenshotNeo can return a PDF from one GET request. A local file:/// path is visible only to your machine, so publish the file at an authenticated or temporary HTTPS URL first. See the ScreenshotNeo API documentation for options.
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com/report.html \
-d format=pdf \
-o report.pdf
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={
"access_key": "YOUR_API_KEY",
"url": "https://example.com/report.html",
"format": "pdf",
},
timeout=90,
)
r.raise_for_status()
open("report.pdf", "wb").write(r.content)
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://example.com/report.html',
format: 'pdf'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`ScreenshotNeo returned ${res.status}`);
const data = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('report.pdf', data));
ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, failed loads, timeouts, and cache hits are not billed. Its response identifies the page verdict and billing status with X-Page-Verdict and X-Billed headers. You can also use its MCP server with Claude, Cursor, or another MCP client, and configure PDF paper size, margins, orientation, and page ranges. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.
10. FAQ
Can I convert HTML to PDF without uploading it?
Yes. Browser Print, Chrome headless, Puppeteer, and wkhtmltopdf can read a local file directly. A hosted API requires a URL it can reach, so a private local path must first be made available securely.
Which method preserves modern CSS best?
Current Chrome or Puppeteer generally matches modern browser rendering most closely. Validate the exact document because CSS support and print behavior still vary by version.
Why does my PDF have different colors?
Printing may disable background graphics or apply print CSS. Enable background graphics and inspect your @media print rules.
Should I use print CSS or screen CSS?
Use print CSS for paper-oriented output. In Puppeteer, print CSS is the default; choose screen CSS explicitly only when that is the intended design.


