In Node.js, install @ironsoftware/ironpdf, convert HTML with the asynchronous PdfDocument.fromHtml() or PdfDocument.fromUrl() method, then call saveAs(). IronPDF renders through its Chrome-based IronPdfEngine, so it can process HTML, CSS and client-side JavaScript on the server. A matching engine binary is required, and unlicensed output contains a watermark.
The shortest working example
Create a Node.js project, install the package and run this module:
npm init -y
npm i @ironsoftware/ironpdf
import { PdfDocument } from '@ironsoftware/ironpdf';
const pdf = await PdfDocument.fromHtml('<h1>Hello from IronPDF!</h1>');
await pdf.saveAs('html-to-pdf.pdf');
The API is asynchronous. fromHtml() accepts an HTML string or a local path, and saveAs() writes the generated document. Use a recent Node.js runtime supported by the package; the current package metadata and documentation state Node.js 12 or newer, with Windows, Linux, macOS and Docker support.
Install IronPDF and its rendering engine
Install the npm package
The package name is exactly @ironsoftware/ironpdf. The package version reported for 2026 is 2026.8.1. Pin the version in production rather than allowing an unreviewed update to change rendering behavior.
#1 Best Overall
npm install @ironsoftware/[email protected]
IronPDF is not only JavaScript code. It requires a matching IronPDF Engine binary. On first execution, the package attempts to download that binary automatically. This is convenient on a development machine but can fail in a build container, an isolated network or a production host without outbound access.
Install an engine package explicitly when networking is restricted
Use the engine package that matches both your operating system and the IronPDF package version. Official package names include:
@ironsoftware/ironpdf-engine-windows-x64@ironsoftware/ironpdf-engine-linux-x64@ironsoftware/ironpdf-engine-macos-x64@ironsoftware/ironpdf-engine-macos-arm64
The API reference warns that the IronPDF and engine versions must match. In CI, install the chosen engine as a regular dependency, cache the package in your build system, and verify the target architecture before deploying.
Use an ES module project
The examples use import. Either save the file with an .mjs extension or add "type": "module" to package.json:
{
"type": "module",
"dependencies": {
"@ironsoftware/ironpdf": "2026.8.1"
}
}
Convert each supported HTML source
Convert an HTML string
This is useful when a template engine, database or application code already produces the markup. Include complete HTML, including a <head> section, when styles or metadata matter.
Rank #2
import { PdfDocument } from '@ironsoftware/ironpdf';
const html = `<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>body { font-family: sans-serif; } h1 { color: #245; }</style>
</head>
<body>
<h1>Invoice</h1>
<p>Generated by a Node.js service.</p>
</body>
</html>`;
const pdf = await PdfDocument.fromHtml(html);
await pdf.saveAs('./output/invoice.pdf');
Convert a local HTML file
Pass the path to fromHtml(). Relative images, stylesheets and scripts must be reachable from the runtime environment and their paths must resolve correctly there, not merely on your workstation.
import { PdfDocument } from '@ironsoftware/ironpdf';
const pdf = await PdfDocument.fromHtml('./reports/index.html');
await pdf.saveAs('./output/report.pdf');
Convert a URL
fromUrl() loads an online page through the IronPdfEngine and returns a PDF document.
import { PdfDocument } from '@ironsoftware/ironpdf';
const pdf = await PdfDocument.fromUrl('https://example.com');
await pdf.saveAs('./output/example.pdf');
The server running Node.js must be able to resolve the hostname, establish the connection and retrieve every asset the page needs. A URL that works in your desktop browser can still fail in a private subnet, an authenticated environment or a container with restricted egress.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsConvert an HTML ZIP archive
The tutorial also documents fromZip() for an archive containing the main HTML file and its accompanying assets. This is useful when you need to move a self-contained page between build stages without relying on public URLs. Keep the archive’s relative paths intact so images, stylesheets and scripts resolve as they did when the archive was created.
import { PdfDocument } from '@ironsoftware/ironpdf';
const pdf = await PdfDocument.fromZip('./package/site.zip');
await pdf.saveAs('./output/site.pdf');
Remove the IronPDF watermark with a license
Without a valid license key, IronPDF brands generated or modified documents with a watermark. Configure the global license before calling conversion methods:
import { IronPdfGlobalConfig, PdfDocument } from '@ironsoftware/ironpdf';
const config = IronPdfGlobalConfig.getConfig();
config.licenseKey = process.env.IRONPDF_LICENSE_KEY;
const pdf = await PdfDocument.fromHtml('<h1>Licensed output</h1>');
await pdf.saveAs('./output/licensed.pdf');
Set IRONPDF_LICENSE_KEY in the deployment secret store rather than committing it to source control. Initialize the configuration once during process startup, before any code creates or modifies a document. If the key is missing, expired or invalid, test output can still be produced but will carry the watermark; production use requires a paid license. Iron Software’s documentation lists licensing from $999, but pricing can change, so confirm the current terms and the free 30-day trial directly with Iron Software before purchase.
What the Chrome-based renderer does
IronPDF for Node.js uses a Chrome-based IronPdfEngine. That design is intended for server-side Node.js applications, APIs and microservices rather than code executed inside a visitor’s browser. It renders HTML and CSS, can run client-side scripts, and is described as supporting images, hyperlinks, forms and complex CSS when the page’s assets are available and paths resolve.
JavaScript-rendered pages
For a page that builds its content after the initial response, use fromUrl() and ensure the target can complete its normal client-side work in the server environment. A failed API request, a script that depends on browser-only permissions, or an asset blocked by network policy can leave the PDF incomplete even though the URL itself is valid.
External assets and authentication
Local files, fonts, images, stylesheets and API responses must be available to the engine. For private pages, design an authenticated server-side route or a self-contained ZIP rather than exposing credentials in a public URL. Validate the final document for missing images and fonts; a successful method call only proves that a PDF was written, not that every visual dependency loaded.
A production conversion pattern
Keep rendering work off the request thread that handles latency-sensitive application traffic. The API reference describes rendering as computationally intensive and recommends delegating it to the server. A queue worker or dedicated conversion service lets you control concurrency and retry failures without tying up user-facing requests.
Rank #4
- Validate the source. Accept only trusted templates, approved URLs or sanitized HTML. Reject unexpected schemes and paths before passing them to the renderer.
- Prepare dependencies. Bundle or prefetch assets that must be deterministic. In a restricted environment, install the matching engine package during the image build.
- Initialize licensing. Load the key from a secret and configure
IronPdfGlobalConfigbefore the first conversion. - Render asynchronously. Call
fromHtml(),fromUrl()orfromZip()withawait; do not block the event loop with synchronous file or process work around the conversion. - Persist and verify. Save to a unique path, check that the file exists and has a non-zero size, and record the source identifier and engine/package versions for diagnosis.
- Control concurrency. Limit simultaneous jobs according to the CPU and memory available to the engine. Increase workers gradually while watching for timeouts or memory pressure.
Example worker function
import { mkdir } from 'node:fs/promises';
import { PdfDocument } from '@ironsoftware/ironpdf';
await mkdir('./output', { recursive: true });
export async function renderInvoice(html, id) {
if (typeof html !== 'string' || html.length === 0) {
throw new TypeError('html must be a non-empty string');
}
const safeId = String(id).replace(/[^a-z0-9_-]/gi, '_');
const destination = `./output/invoice-${safeId}.pdf`;
const pdf = await PdfDocument.fromHtml(html);
await pdf.saveAs(destination);
return destination;
}
try {
const path = await renderInvoice('<h1>Invoice 42</h1>', '42');
console.log(`Created ${path}`);
} catch (error) {
console.error('PDF conversion failed:', error);
process.exitCode = 1;
}
Troubleshooting common failures
| Symptom | Likely cause | Fix |
|---|---|---|
| Module loads but conversion fails immediately | The IronPDF Engine binary is absent, blocked from downloading, or does not match the package. | Install the OS-specific engine package explicitly, confirm architecture, and align its version with @ironsoftware/ironpdf. |
| Works locally but fails in CI or Docker | The build image has no outbound network access or lacks the required native runtime dependencies. | Install the matching engine during image creation, use a supported base image, and test the same image used in deployment. |
| PDF contains a watermark | No valid license was configured before conversion. | Set IronPdfGlobalConfig.getConfig().licenseKey from a secret before calling fromHtml, fromUrl or fromZip. |
| Text appears but images or CSS are missing | Relative paths resolve differently on the server, or external assets are unreachable. | Use absolute reachable paths, package assets with fromZip(), and verify network and file permissions from the worker. |
| URL PDF is blank or incomplete | Scripts, API calls or redirects did not finish, or the server cannot reach the page. | Check the page from the deployment network, make required data available without interactive browser permissions, and prefer a self-contained HTML source when determinism matters. |
| Jobs time out or the host becomes unresponsive | Chrome-based rendering is CPU- and memory-intensive, especially with many concurrent pages. | Move work to a queue, reduce concurrency, split very large jobs and monitor worker resource usage. |
Performance, reliability and cost decisions
Choose the source that minimizes variability
A literal HTML string has the fewest network dependencies. A local file is convenient but depends on correct filesystem paths. A URL is appropriate when the page is already published, yet it introduces DNS, TLS, authentication, redirects and third-party asset dependencies. A ZIP archive gives you a portable set of files and is often the most repeatable option for a report with many local assets.
Free tools Windows power users keep installed
One-click scans. No signup required.
Plan for rendering cost
Every conversion starts a browser-engine rendering workload. Batch requests through workers, cap parallelism and avoid generating the same document repeatedly when an application can safely reuse an existing PDF. Measure your own templates because page count, images, fonts and scripts affect resource consumption; the available documentation does not provide a universal pages-per-second figure.
Keep versions reproducible
Record the npm package and engine versions with each deployment. A package update without its matching engine can break startup, while a rendering-engine update can alter layout. Test representative documents after upgrades, including pages with JavaScript, external images, forms and long tables.
When a screenshot API is a better fit
IronPDF is a server-side PDF renderer for HTML sources. If the real requirement is a clean image of a public web page, a screenshot API avoids maintaining a browser engine and can return PNG, JPEG or WebP directly. ScreenshotNeo is the first alternative to try because it removes cookie banners, newsletter popups and chat widgets before capture, bills only clean shots, and has a $5 paid plan.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
ScreenshotNeo accepts one GET request and returns a screenshot or PDF. Cookie and consent banners, popups and chat widgets are removed before the shot; bot checks, blank pages and failed loads are not billed. Its MCP server gives Claude, Cursor and other MCP clients take_screenshot, get_page_info and capture_pdf tools. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000 shots.
Recommended Free Tools
Use the API documentation at https://screenshotneo.com/docs/ for parameters and authentication.
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 returned ${res.status}`);
const file = await res.arrayBuffer();
await Bun.write('shot.webp', file);
ScreenshotNeo also supports full-page capture with lazy images loaded, CSS-element capture, dark mode, device presets, custom viewports, retina scale, PDF paper and margin settings, custom CSS and JavaScript, click-before-capture actions, selector waits, network-idle waits, request blocking, headers, cookies, user agents, authorization, timezone and geolocation, transparent backgrounds, resizing, configurable caching, signed links, asynchronous webhooks, bulk capture of up to 100 URLs per call, a usage API and an OpenAPI specification. Create a free ScreenshotNeo account to start with 1,000 screenshots per month and no card.
Best Value
FAQ
Can the same code generate both a PDF and an image?
IronPDF’s documented Node.js API produces PDF documents. Use a separate image or screenshot service when your output contract is PNG, JPEG or WebP rather than PDF.
Should I download the engine at application startup?
For repeatable deployments, install the matching engine package while building the application image. Relying on a first-run download makes startup dependent on outbound network access.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Is the npm download count a quality guarantee?
No. The npm page reports 3,492 weekly downloads for the 2026 package listing, but download volume does not establish rendering fidelity for your templates. Validate your own representative documents before adopting an upgrade.
Frequently Asked Questions
Does IronPDF run inside a browser bundle?
It is positioned for server-side Node.js applications, APIs and microservices and relies on the Chrome-based IronPdfEngine, so keep conversion code on a trusted backend rather than shipping it to browser users.
What should I archive when diagnosing a failed conversion?
Keep the input HTML or ZIP, package and engine versions, deployment image identifier, renderer error, output path and a record of which external assets were reachable. That set usually distinguishes source problems from environment problems.
Can I use a private page as the URL source?
Only when the server-side renderer can authenticate and reach every required resource. For sensitive or highly variable pages, a controlled HTML string or self-contained ZIP is easier to secure and reproduce.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

