ScreenshotNeo

BlogHTML to image & PDF

How to Convert HTML to PDF with IronPDF for JavaScript

Convert HTML strings, files, URLs, and JavaScript-rendered pages to PDF in Node.js with IronPDF, including engines, licensing, deployment, and fixes.

By the ScreenshotNeo team1 October 20268 min read

Direct answer: install @ironsoftware/ironpdf, import PdfDocument, call await PdfDocument.fromHtml(...) for HTML or await PdfDocument.fromUrl(...) for a web page, then write the result with await pdf.saveAs(...). IronPDF renders through its Chrome-based IronPdfEngine, so it can process CSS and client-side JavaScript in a server-side Node.js application.

This guide covers HTML strings, local files, URLs, ZIP archives, engine installation, licensing, deployment, troubleshooting, performance, and a hosted alternative when you do not want to operate a browser engine.

Install IronPDF for Node.js

Create a project and install the npm package:

mkdir ironpdf-html-pdf
cd ironpdf-html-pdf
npm init -y
npm i @ironsoftware/ironpdf

The package needs a matching IronPDF Engine binary. On first execution it attempts to download the engine. In restricted build or production environments, install the OS-specific engine package explicitly and keep its version aligned with @ironsoftware/ironpdf. Official package names include:

  • @ironsoftware/ironpdf-engine-windows-x64
  • @ironsoftware/ironpdf-engine-linux-x64
  • @ironsoftware/ironpdf-engine-macos-x64
  • @ironsoftware/ironpdf-engine-macos-arm64

See the IronPDF npm package and IronPDF’s Node.js documentation for the current package and engine details.

Convert an HTML string

This is the smallest complete program. Save it as index.mjs and run node index.mjs:

import { PdfDocument } from "@ironsoftware/ironpdf";

const html = `
  <!doctype html>
  <html>
    <head>
      <meta charset="utf-8">
      <style>
        body { font-family: Arial, sans-serif; margin: 40px; }
        h1 { color: #1f2937; }
      </style>
    </head>
    <body>
      <h1>Hello from IronPDF</h1>
      <p>This PDF was generated from an HTML string.</p>
    </body>
  </html>`;

const pdf = await PdfDocument.fromHtml(html);
await pdf.saveAs("html-string.pdf");

Convert a local HTML file

Pass the path to fromHtml. Resolve paths from the process working directory explicitly so deployments do not depend on where the command was started:

import path from "node:path";
import { fileURLToPath } from "node:url";
import { PdfDocument } from "@ironsoftware/ironpdf";

const here = path.dirname(fileURLToPath(import.meta.url));
const input = path.join(here, "templates", "invoice.html");
const output = path.join(here, "out", "invoice.pdf");

const pdf = await PdfDocument.fromHtml(input);
await pdf.saveAs(output);

Relative image, stylesheet, font, and script URLs must resolve from the runtime environment. Prefer absolute file paths or a predictable base directory, and make sure those assets are included in the container or deployment artifact.

Convert a URL or JavaScript-rendered page

Use fromUrl when the page is served by an HTTP server or needs its client-side JavaScript to run:

import { PdfDocument } from "@ironsoftware/ironpdf";

const pdf = await PdfDocument.fromUrl("https://example.com");
await pdf.saveAs("web-page.pdf");

IronPDF uses a Chrome-based engine and is intended for server-side Node.js workloads. The target must be reachable from the machine running Node.js, and all required assets must load there. A page that works in your desktop browser can still fail in a server, container, private network, or proxy environment.

Convert an HTML ZIP archive

When HTML and its assets need to travel together, use fromZip with an archive containing the main HTML file and its referenced resources. Keep relative paths inside the archive consistent with the markup:

import { PdfDocument } from "@ironsoftware/ironpdf";

const pdf = await PdfDocument.fromZip("./report-assets.zip");
await pdf.saveAs("report.pdf");

This is useful for generated reports containing local images, stylesheets, fonts, and scripts. Verify that the archive’s entry HTML file and asset paths match the structure expected by the renderer.

License the renderer and remove the watermark

Without a valid license key, IronPDF brands generated or modified documents with a watermark. Configure the global license before calling other IronPDF functions:

import { IronPdfGlobalConfig } from "@ironsoftware/ironpdf";

const config = IronPdfGlobalConfig.getConfig();
config.licenseKey = process.env.IRONPDF_LICENSE_KEY;

if (!config.licenseKey) {
  throw new Error("IRONPDF_LICENSE_KEY is not set");
}

Set the environment variable through your secret manager or deployment configuration; do not commit the key to source control. IronPDF offers a free trial, while production use requires a paid license. Licensing terms and prices can change, so confirm the current terms in the vendor’s documentation before purchasing.

Build a reusable conversion function

A small wrapper gives every call consistent error handling and output paths:

import { mkdir } from "node:fs/promises";
import path from "node:path";
import { PdfDocument } from "@ironsoftware/ironpdf";

export async function htmlToPdf({ html, outputPath }) {
  if (!html || typeof html !== "string") {
    throw new TypeError("html must be a non-empty string");
  }

  await mkdir(path.dirname(outputPath), { recursive: true });
  const pdf = await PdfDocument.fromHtml(html);
  await pdf.saveAs(outputPath);
  return outputPath;
}

try {
  await htmlToPdf({
    html: "<h1>Invoice</h1>",
    outputPath: "./out/invoice.pdf"
  });
  console.log("PDF written");
} catch (error) {
  console.error("PDF conversion failed:", error);
  process.exitCode = 1;
}

Rendering details that affect output

  • CSS: complex CSS is rendered by the Chrome-based engine, but unsupported browser features or missing resources can change layout.
  • JavaScript: client-side scripts can run for URL and HTML sources, subject to the page finishing its work before conversion.
  • Images and fonts: remote resources need network access; local resources need correct paths and file permissions.
  • Forms and hyperlinks: the tutorial documents support for forms, links, images, and scripts, but page-specific behavior still depends on the rendered HTML.
  • Dynamic data: render after your application has inserted the final values. For server-generated reports, producing complete HTML before calling IronPDF is usually more predictable than relying on late browser mutations.

Deployment checklist

  1. Pin compatible versions of @ironsoftware/ironpdf and the IronPDF Engine package.
  2. Install the correct engine for the deployment OS and CPU architecture.
  3. Allow the build or runtime to obtain the engine, or include the explicit engine package where outbound downloads are blocked.
  4. Confirm that the process can write to the destination directory.
  5. Confirm DNS, TLS, proxy, and firewall access for every URL and external asset.
  6. Provide fonts and other system dependencies required by your target layout.
  7. Set IRONPDF_LICENSE_KEY through a secret rather than source code.
  8. Run conversion on the server or in a worker. Rendering is computationally intensive and is not intended for browser-side execution.

Common errors and fixes

Symptom Likely cause Fix
Engine download fails Build or runtime has no outbound network access. Install the matching OS-specific IronPDF Engine package during the build and verify package versions.
Version mismatch error The .NET/Node wrapper and engine binaries are on different versions. Align @ironsoftware/ironpdf and the engine package, then reinstall dependencies cleanly.
Watermark appears No valid license was configured before conversion. Set IronPdfGlobalConfig.getConfig().licenseKey before calling fromHtml, fromUrl, or fromZip.
Blank or incomplete PDF HTML, scripts, or assets did not load in the server environment. Check URLs, relative paths, permissions, proxy rules, and whether the page depends on browser-only APIs.
Images or fonts missing Relative paths resolve differently from the process working directory, or remote access is blocked. Use stable absolute paths or package the assets in a ZIP; test resource URLs from the deployment host.
URL conversion cannot connect The target is private, blocked by a firewall, or requires authentication. Make the target reachable from the rendering host and provide the required authenticated page through an accessible server-side flow.
Process is slow or runs out of memory Many concurrent Chrome-based renders or very large pages. Move jobs to a worker queue, limit concurrency, reuse the service process, and reduce unnecessary page assets.
Output path error Parent directory does not exist or is not writable. Create the directory with mkdir(..., { recursive: true }) and verify container permissions.

Performance, reliability, and cost considerations

Each conversion starts substantial browser rendering work, so latency and memory use depend on page size, scripts, images, fonts, and concurrency. For reliable services:

  • Use a job queue for bursts instead of starting unlimited conversions in request handlers.
  • Set an application-level timeout and record the source URL, duration, output size, and error type.
  • Retry transient network failures with a bounded backoff, but avoid blindly retrying invalid HTML or unreachable private URLs.
  • Keep large assets and unnecessary third-party scripts out of print templates.
  • Generate deterministic HTML and store the source or a content hash when you need reproducibility.
  • Use a worker process so a failed render cannot take down the API process handling unrelated requests.

IronPDF is commercial software. The vendor’s documentation describes a free trial and paid production licensing; confirm current pricing before budgeting. Your infrastructure cost also includes CPU, memory, storage, and network traffic for the Chrome-based engine.

Or skip the browser setup

If you need a screenshot or PDF from a URL without packaging and operating a browser engine, ScreenshotNeo provides a hosted GET API. It accepts a URL and returns PNG, JPEG, WebP, or PDF. Before capture it accepts consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers.

See the ScreenshotNeo API documentation for all options, including PDF paper size, margins, orientation, page ranges, waiting rules, custom headers and cookies, blocking, caching, signed links, asynchronous jobs, bulk capture, and usage data.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`ScreenshotNeo HTTP ${res.status}`);
await Bun.write("shot.webp", res);

ScreenshotNeo also has an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

FAQ

Can IronPDF convert a raw HTML string?

Yes. Pass the string to PdfDocument.fromHtml, await the result, and call saveAs.

Can it convert a URL that renders content with JavaScript?

Yes. fromUrl uses the Chrome-based IronPdfEngine, provided the page and its assets are reachable from the server.

How do I convert an HTML project with local assets?

Use a stable file path with fromHtml, or package the HTML and assets in a ZIP and use fromZip.

Why does my PDF have an IronPDF watermark?

The output was generated without a valid license key. Configure the global license before conversion.

Does IronPDF run in a browser?

It is designed for server-side Node.js applications, APIs, and microservices. Rendering in a worker or server process avoids exposing the engine to browser clients.

What should I use when I only need a hosted capture of a public URL?

Try ScreenshotNeo when you want a one-call capture API, automatic consent and popup cleanup, usage-based billing that excludes failed captures, or MCP tools for AI agents.