# HTML to PDF API: HTML or URL to PDF, A4 / Letter (`ucchiii/html-to-pdf`) Actor

Turn HTML or public URLs into PDF files: A4, Letter and more, margins, header and footer, page numbers, background printing. Each PDF is stored with a signed link. Empty input = free demo. $0.008 per PDF; failed conversions are not charged.

- **URL**: https://apify.com/ucchiii/html-to-pdf.md
- **Developed by:** [Actor Factory](https://apify.com/ucchiii) (community)
- **Categories:** Developer tools, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.60 / 1,000 pdfs

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

**HTML or public URL to PDF, A4 / Letter, header and footer**

**For developers, finance and ops teams, and no-code builders who need invoices, reports and web pages as PDF files without running their own headless browser.**
Send an HTML string or a list of public URLs and get one **PDF** per item, stored in your key-value store with a **signed download link** in the dataset.
Choose **A4 or Letter**, margins, a header and footer with page numbers, and whether backgrounds are printed.
**$0.008 per PDF — and you only pay when the PDF was stored successfully.** Leave the input empty for a free demo.

### Use cases

- **Invoices, quotes and certificates**: render your own HTML template to a print-ready PDF (backgrounds and brand colors kept)
- **Reports and exports**: turn an HTML report into a paginated PDF with "Page 1 of N" and a date in the header
- **Archiving public pages**: save a documentation page, article or policy as PDF for records (only pages you may copy)
- **Automation**: call it from Make, Zapier, n8n or your own code and use the `fileUrl` field of the result

### Features

- Input: one HTML document, a JSON list of HTML documents, public page URLs, or any mix (up to 50 items per run)
- Paper: A4, Letter, Legal, A3, A5; portrait or landscape; or the page's own `@page` size
- Margins per side in millimetres (0 to 50)
- **Header and footer**: plain text with `{page}`, `{pages}`, `{date}`, `{title}`, `{url}` placeholders (or small HTML), plus a one-click page-number footer
- **Background printing** on by default (colors and images), switchable
- Print or screen CSS, scale 0.1 to 2, page ranges such as `1-3`, wait strategy and extra delay for charts
- Optional: hide cookie banners with CSS, block common ad and tracking domains
- Output: the PDF in the key-value store plus a signed link, page count, size and title in the dataset

### Price

| Event | Price |
On the Apify Free plan, the $5 of monthly platform credit covers about **625 PDFs** a month — enough to try it on real data.
|---|---|
| `pdf` (one PDF stored successfully) | $0.008 |

**Not charged on failures:** timeouts, navigation errors, HTTP 4xx/5xx pages, blocked or unsupported URLs, invalid URLs, HTML that is too large and files that could not be stored. A row is charged only after its PDF is stored. If you set a maximum cost per run, the run stops when the limit is reached. The empty-input demo is free.

### Input

HTML to PDF (invoice on US Letter with page numbers):

```json
{
  "html": "<h1>Invoice #2042</h1><p>Total: $1,280.00</p>",
  "paperFormat": "Letter",
  "headerTemplate": "Example Co. - Invoice #2042",
  "pageNumbers": true,
  "printBackground": true
}
```

Public URLs to PDF:

```json
{
  "urls": ["https://docs.python.org/3/tutorial/index.html"],
  "paperFormat": "A4", "marginTopMm": 20, "marginBottomMm": 20, "pageNumbers": true, "hideCookieBanners": true
}
```

Other fields: `htmlDocuments` (JSON array of HTML strings), `landscape`, `marginRightMm`, `marginLeftMm`, `footerTemplate`, `mediaType` (`print` or `screen`), `scale`, `preferCssPageSize`, `pageRanges`, `waitUntil`, `delayMs`, `blockAds`.
If you set a header or footer, the top or bottom margin is raised to at least 15 mm so the text does not overlap the page. There is no login, cookie, header or form input for URLs.

### Output

One dataset row per item:

```json
{
  "source": "html", "label": "html-1", "url": null, "finalUrl": null, "status": null,
  "title": "Invoice #2042", "paperFormat": "Letter", "pages": 1, "bytes": 20616,
  "fileUrl": "https://api.apify.com/v2/key-value-stores/<storeId>/records/pdf-001-1a2b3c4d5e.pdf?signature=...",
  "key": "pdf-001-1a2b3c4d5e.pdf", "createdAt": "2026-10-25T09:00:00+00:00", "error": null
}
```

The PDF itself is stored in the run's default key-value store as `application/pdf`. `fileUrl` is a signed link that opens without an API token; anyone who has the link can open that file, so share it only where you want to. For URL items `url`, `finalUrl` (after redirects) and `status` are filled in. Failed rows have `fileUrl: null` and `error` set to one of: `invalid_url`, `unsupported_domain`, `blocked_private_address`, `timeout`, `navigation_failed`, `http_<status>`, `http_error`, `html_too_large`, `render_failed`, `pdf_too_large`, `storage_failed`. Demo rows carry `_demo: true`.

### Limits

- 50 items per run (HTML documents and URLs together); extra items are ignored with a warning
- 3 conversions at a time overall, at most 2 at a time per host, 45 s per item; one HTML document up to 2,000,000 characters; one PDF up to 30 MB
- Public pages only: no login, no cookies, no custom headers, no form filling. Pages behind a consent wall that CSS cannot hide, or behind bot protection, may print the wall (or fail)
- Facebook, Instagram, X/Twitter, TikTok, LinkedIn and Threads are refused (`unsupported_domain`)
- Very dynamic pages may need `waitUntil: networkidle` or an extra delay; page content that only appears after a click is not captured

### Safety

Only `http`/`https` URLs are accepted. Every host is resolved first and **localhost, private ranges, link-local (169.254.169.254) and other internal addresses are never contacted** — for the URL itself, each redirect hop and every request a page or your HTML makes (images, fonts, scripts); `file:` addresses are blocked too. Known limit: a DNS answer that changes between the check and the connection (DNS rebinding) is not detected. You are responsible for having the right to convert the pages and content you submit (site terms, copyright, personal data in your own HTML). Files stay in your own storage; nothing is kept by the developer.

If this Actor saved you time, a rating on the Store helps others find it.

### More tools from the same developer

- [Website Screenshot](https://apify.com/ucchiii/website-screenshot) — screenshots and PDFs of web pages: full page, mobile, element, no cookie banners
- [HTML to Markdown Converter for LLM and RAG](https://apify.com/ucchiii/html-to-markdown) — clean Markdown from URLs, raw HTML or datasets for LLM and RAG
- [PDF Extractor](https://apify.com/ucchiii/pdf-extractor) — PDF to text, tables and metadata from public PDF URLs, page by page
- [Google Sheets Export](https://apify.com/ucchiii/google-sheets-export) — export an Apify dataset to Google Sheets

# Actor input Schema

## `html` (type: `string`):

A complete HTML document or fragment to turn into one PDF (up to 2,000,000 characters). Inline CSS works; external images and fonts are loaded only from public http(s) addresses. Leave HTML, HTML documents and URLs all empty for a free demo (a sample invoice, not charged).

## `htmlDocuments` (type: `array`):

Optional. A JSON array of HTML strings, one PDF each, for example \["<h1>A</h1>", "<h1>B</h1>"]. Counted together with HTML and URLs toward the 50-item limit per run.

## `urls` (type: `array`):

Public http(s) pages to save as PDF, one PDF each. No login, cookies or custom headers. At most 50 items per run (HTML and URLs together). Social networks (Facebook, Instagram, X/Twitter, TikTok, LinkedIn, Threads) and internal addresses are refused.

## `paperFormat` (type: `string`):

Paper size of every page: 'A4' (default), 'Letter', 'Legal', 'A3' or 'A5'. Ignored when 'Use the page's own size' is on and the page sets @page size.

## `landscape` (type: `boolean`):

Print pages sideways (wide instead of tall). Default off (portrait).

## `marginTopMm` (type: `integer`):

Top margin in millimetres, 0 to 50 (default 15). If a header is set, at least 15 is used so the header does not overlap the text.

## `marginRightMm` (type: `integer`):

Right margin in millimetres, 0 to 50 (default 15).

## `marginBottomMm` (type: `integer`):

Bottom margin in millimetres, 0 to 50 (default 15). If a footer or page numbers are set, at least 15 is used so the footer does not overlap the text.

## `marginLeftMm` (type: `integer`):

Left margin in millimetres, 0 to 50 (default 15).

## `headerTemplate` (type: `string`):

Optional text printed at the top of every page. Plain text can use {page}, {pages}, {date}, {title} and {url}, for example 'Quarterly report - {date}'. You can also pass small HTML (inline styles, data: images; no scripts). Up to 5,000 characters.

## `footerTemplate` (type: `string`):

Optional text printed at the bottom of every page, with the same {page}, {pages}, {date}, {title} and {url} placeholders, for example 'Page {page} of {pages}'. Overrides the Page numbers option. Up to 5,000 characters.

## `pageNumbers` (type: `boolean`):

Print 'Page 1 of 3' centered in the footer. Ignored when you set your own footer.

## `printBackground` (type: `boolean`):

Print background colors and background images. On by default; turn off for plain black-on-white output that saves ink.

## `mediaType` (type: `string`):

Which CSS rules apply: 'print' (default) uses @media print rules like a browser's Print dialog; 'screen' keeps the on-screen look.

## `scale` (type: `number`):

Zoom of the page content, 0.1 to 2 (default 1). Use 0.8 to fit wide pages onto the paper.

## `preferCssPageSize` (type: `boolean`):

Use the paper size from the page's CSS (@page { size: ... }) instead of the Paper size option, when the page sets one. Default off.

## `pageRanges` (type: `string`):

Optional. Keep only some pages, for example '1-3' or '1,3,5-7'. Empty (default) keeps all pages. A value that is not a valid range is ignored.

## `waitUntil` (type: `string`):

When the page counts as loaded: 'load' (default), 'domcontentloaded' (fastest) or 'networkidle' (slowest, for late content). Each item has a 45 second limit.

## `delayMs` (type: `integer`):

Wait this long after loading before printing, 0 to 10000 ms. Useful for charts and animations.

## `blockAds` (type: `boolean`):

Block requests to a small built-in list of common ad and tracking domains. Not a complete filter.

## `hideCookieBanners` (type: `boolean`):

Hide common consent pop-ups (OneTrust, Cookiebot, Didomi, Quantcast, TrustArc, Usercentrics, CookieYes, Osano and generic cookie banners) with CSS. Nothing is clicked or accepted.

## Actor input object example

```json
{
  "html": "<h1>Invoice #1001</h1><p>Thank you for your order.</p>",
  "paperFormat": "A4",
  "landscape": false,
  "marginTopMm": 15,
  "marginRightMm": 15,
  "marginBottomMm": 15,
  "marginLeftMm": 15,
  "pageNumbers": false,
  "printBackground": true,
  "mediaType": "print",
  "scale": 1,
  "preferCssPageSize": false,
  "waitUntil": "load",
  "delayMs": 0,
  "blockAds": false,
  "hideCookieBanners": false
}
```

# Actor output Schema

## `results` (type: `string`):

One item per HTML document or URL: source, label, status, title, paperFormat, pages, bytes, fileUrl (signed link to the PDF), createdAt, error.

## `files` (type: `string`):

The PDF files in the run's key-value store.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "html": "<h1>Invoice #1001</h1><p>Thank you for your order.</p>"
};

// Run the Actor and wait for it to finish
const run = await client.actor("ucchiii/html-to-pdf").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "html": "<h1>Invoice #1001</h1><p>Thank you for your order.</p>" }

# Run the Actor and wait for it to finish
run = client.actor("ucchiii/html-to-pdf").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "html": "<h1>Invoice #1001</h1><p>Thank you for your order.</p>"
}' |
apify call ucchiii/html-to-pdf --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ucchiii/html-to-pdf"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/Wn926yGbRhskXfQ4e/builds/IvCOfBi9qRdYkhUEM/openapi.json
