# URL to PDF: Bulk Webpage & HTML to PDF, Failed Free (`altronite/url-to-pdf`) Actor

Webpage, website and HTML page to PDF in bulk from URLs: A4, Letter or Legal, margins, page numbers, optional merge. Failed URLs are never charged.

- **URL**: https://apify.com/altronite/url-to-pdf.md
- **Developed by:** [Altronite](https://apify.com/altronite) (community)
- **Categories:** Developer tools, Automation, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 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.

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

## URL to PDF: Bulk Webpage & HTML to PDF, Failed Free

Webpage to PDF, HTML page to PDF and website to PDF, in bulk. Turn a list of public URLs into **print-ready PDFs**: A4, Letter or Legal, portrait or landscape, your margins, optional **page numbers**, and the site's print or screen styles. Optionally **merge everything into one PDF** with a bookmark per URL. Common cookie banners are hidden with CSS and robots.txt is respected by default. A URL that can't be converted becomes an error row with the reason, is **never charged**, and doesn't stop the run.

Need PNG or JPEG screenshots instead? Use [Website Screenshot API](https://apify.com/altronite/website-screenshot).

![One link in, one PDF row out](https://api.apify.com/v2/key-value-stores/aZVzOVjoal7bHifMa/records/url-to-pdf-demo.gif)

### Who it's for

- **Research, legal and compliance**: archive public pages, articles and disclosures as dated PDFs.
- **Teams sharing reading packs**: merge a list of articles or docs pages into one bookmarked PDF.
- **Reporting**: print dashboards or wide tables on landscape Letter with page numbers.
- **AI agents and pipelines**: turn URLs into files a document tool can read; every row has a `status` field to branch on.

### Quick recipes: convert a webpage to PDF in bulk

Copy one of these into the **Input** tab (JSON view) and press **Start**.

**A4 PDFs in bulk** (one PDF per URL, the site's print styles, 10 mm margins):

```json
{ "urls": ["https://example.com", "https://example.org", "https://example.net"] }
```

**US Letter, landscape, with page numbers** (wide pages, "1 / 12" footer):

```json
{ "urls": ["https://en.wikipedia.org/wiki/PDF"], "paperFormat": "Letter", "landscape": true, "pageNumbers": true }
```

**Merged into one PDF** (in input order, one bookmark per URL; the single PDFs are kept too):

```json
{ "urls": ["https://example.com", "https://en.wikipedia.org/wiki/PDF", "https://apify.com"], "mergeIntoOne": true }
```

**Archive articles as PDFs, waiting for dynamic content** (wait for the network to go quiet, then print):

```json
{ "urls": ["https://example.com/article-1", "https://example.com/article-2"], "waitUntil": "networkidle", "delayMs": 1000, "pageNumbers": true }
```

**Legal-size pages with wider margins** (for forms and documents):

```json
{ "urls": ["https://example.com/form"], "paperFormat": "Legal", "margin": 20 }
```

**Looks like the browser** (screen styles instead of the print stylesheet, no margins):

```json
{ "urls": ["https://example.com"], "mediaType": "screen", "margin": 0 }
```

### What you get

**One dataset row per non-blank URL when the input contains at least one valid URL**, plus one extra row for the merged file when `mergeIntoOne` produced one. PDFs are saved in the run's key-value store and `pdfUrl` links to each. The **Output** tab links to the dataset and the stored files.

```json
{
  "status": "ok",
  "url": "https://en.wikipedia.org/wiki/Portable_Document_Format",
  "finalUrl": "https://en.wikipedia.org/wiki/PDF",
  "httpStatus": 200,
  "title": "PDF - Wikipedia",
  "pdfUrl": "https://api.apify.com/v2/key-value-stores/<store-id>/records/pdf-0001-63672d491ca0.pdf?signature=...",
  "storageKey": "pdf-0001-63672d491ca0.pdf",
  "pages": 24,
  "bytes": 474730,
  "paperFormat": "A4",
  "takenAt": "2026-10-07T20:20:14+00:00"
}
```

`finalUrl` is the address after redirects; `title` is cut at 300 characters; `pages` is the page count of the PDF; `bytes` is the file size; `takenAt` is the UTC row creation time.

The merged file has its own row, with `type: "merged"` and no `url`:

```json
{ "status": "ok", "type": "merged", "pdfUrl": "https://api.apify.com/v2/key-value-stores/<store-id>/records/merged.pdf?signature=...", "storageKey": "merged.pdf", "pages": 39, "bytes": 4844835, "sourceCount": 3, "takenAt": "2026-10-07T20:24:17+00:00" }
```

Uncharged error rows have `status`, `url`, `error` (at most 500 characters), `input`, `takenAt` and `scrapedAt` (same time as `takenAt`). Rejected HTTP pages and bot checks also have `httpStatus`.

```json
{ "status": "error", "url": "https://en.wikipedia.org/wiki/ThisPageDoesNotExist", "httpStatus": 404, "error": "The page answered 404", "input": "https://en.wikipedia.org/wiki/ThisPageDoesNotExist", "takenAt": "2026-10-07T20:21:01+00:00", "scrapedAt": "2026-10-07T20:21:01+00:00" }
```

### Use it from code or an AI agent

Run this actor with the Apify API, the Apify Python or JavaScript clients, or as a tool through the Apify MCP server. Pass the same input JSON through any of these. Check `status` on each row, then fetch `pdfUrl` for `ok` rows; `error` rows explain what failed.

Minimal input:

```json
{ "urls": ["https://example.com"] }
```

### How to use it

1. **URLs**: one per line, up to 500 per run. Omit `urls` to use `["https://apify.com"]` for a single test conversion. `example.com` works too: `https://` is added for you.
2. **Paper**: `paperFormat` is `A4` (default), `Letter` or `Legal`; `landscape` (off) turns pages sideways.
3. **Look**: `mediaType` is `print` (default: the site's print stylesheet, often without menus) or `screen` (as in the browser). `printBackground` (on) keeps background colours and images.
4. **Margins and numbers**: `margin` is 0-50 mm on all four sides (default 10). `pageNumbers` (off) adds a centred "page / total" footer; the bottom margin is then at least 12 mm so it fits.
5. **Waiting**: `waitUntil` is `load` (default), `domcontentloaded` or `networkidle`. `delayMs` adds an extra wait after load, up to 30 s.
6. **Merging**: `mergeIntoOne` (off) also joins every delivered PDF, in input order, into `merged.pdf`, with one bookmark per URL named after the page title (or the URL if the page has none). It needs at least 2 delivered PDFs, and their total size must fit the merge limit: 10% of the run's memory (about 102 MB at the default 1 GB).
7. **Options**: `hideCookieBanners` (on), `respectRobotsTxt` (on), `timeoutSecs` (5-180, default 45) and `maxConcurrency` (1-7 pages in parallel, default 2). Allow about 400 MB of memory per parallel page; raise memory for heavy sites or higher concurrency. Memory defaults to 1 GB and goes up to 4,096 MB. The run log ends with a "Peak memory" line to help you size it.

### Pricing

This actor uses **pay-per-event** pricing. The rates are on the actor's Pricing tab:

- `apify-actor-start`: a small charge per run (once at the default 1 GB memory, once per GB if you raise memory).

- `pdf`: charged per PDF delivered, meaning each `ok` URL row.

- `merge`: charged per merged file produced (only with `mergeIntoOne`, and only when the merged file is stored).

- **Not charged:** error rows. That covers HTTP 4xx/5xx pages, detected bot checks, robots.txt skips, invalid URLs, pages that failed to load after the retry, prints that ran past the timeout, a failed merge, and a merge skipped over the memory limit. With fewer than 2 delivered PDFs no merged file is made and `merge` isn't charged.

- **Charged:** delivered PDFs, and the merged file. Successful-status login or paywall pages can still be printed and charged; there is no general login-wall detector.

- A URL listed twice is converted, and charged, twice.

Any URL that fails costs nothing. The start fee is per run, so converting many URLs in one run is the cheapest way to use it. Check the Pricing tab for the current prices.

### Reliability

- **One bad URL never fails the run.** Any failure becomes an error row with the reason.
- **Navigation errors and timeouts, and browser errors while printing, are retried once**, after a short random pause, in a fresh browser context. HTTP rejections, bot checks and robots skips are not retried.
- **Timeouts apply per step.** `timeoutSecs` limits page loading, then PDF printing, separately. A print that runs past the limit is reported, not retried: raise `timeoutSecs` for very long pages.
- **Lazy images**: before printing, native lazy images are switched to load immediately, the page is scrolled at most 20 steps / about 5 s and back to the top, then it waits up to 3 s for images still loading. Images that load later may be missing.
- **Chinese, Japanese, Korean and emoji fonts** are installed, so text doesn't print as empty boxes.
- **Invalid input** (for example, no valid URL at all, or a paper size that isn't offered) stops the run at the start with a message saying what to fix. Nothing is charged.

### FAQ

**How do I convert a webpage to PDF?**
Put the page address in `urls` and run the actor. You get a PDF in the run's key-value store and a `pdfUrl` link in the dataset row. Paste many URLs to convert them all in one run.

**Can I combine several web pages into one PDF?**
Yes. Set `mergeIntoOne` to `true` and the delivered PDFs are joined in input order into `merged.pdf`, with one bookmark per URL. The single PDFs are kept too.

**Can I convert a whole website to PDF?**
It converts the URLs you list; it does not crawl links. Pass the pages you want, up to 500 per run.

**Why does my PDF look different from the website?**
By default it uses the site's **print** styles, which many sites write to hide menus and ads. Set `mediaType` to `screen` to keep the on-screen look. Pages are laid out at paper width, so wide layouts may reflow; try `landscape`.

**Does it respect robots.txt?**
Yes, by default. Before converting a URL, it reads that site's robots.txt (cached per origin) and skips URLs disallowed for all user agents (`User-agent: *`). A robots.txt that returns any 4xx status or can't be reached counts as "allowed"; one that returns a server error (5xx) counts as "disallow everything". Rules use longest-match precedence, `*` wildcards and `$` end anchors; Allow wins ties. The final redirect target is checked before printing if its host differs (after the redirected request has happened). Turn `respectRobotsTxt` off only for pages you have the right to capture.

**Does it get past logins, paywalls or bot protection?**
No. It never logs in, uses no proxy and doesn't evade bot protection. Recognized Cloudflare bot checks become uncharged errors. Other protections may not be recognized.

**How are cookie banners hidden?**
With CSS only; nothing is clicked or accepted. It targets common consent-manager containers and "cookie-consent" patterns. Custom pop-ups may still print.

**Why was my merge skipped?**
The merged file is built in memory, which takes about 5 times the size of the PDFs being joined. To keep the run from running out of memory, merging stops at PDFs totalling 10% of the run's memory (about 102 MB at the default 1 GB). The error row gives the size and the limit. Raise the run's memory (up to 4 GB) or merge fewer URLs per run. The single PDFs are delivered either way.

**Can I convert raw HTML to PDF?**
Not as pasted text. It converts HTML pages from a URL, so put the HTML somewhere reachable (a static host or a public storage link) and pass that URL. It renders in a real Chromium browser, so CSS, web fonts and images print as they do on the page.

**How long are the files kept?**
They follow your Apify plan's data retention for run storage. Download anything you want to keep.

### Limits

- Up to 500 URLs per run, http and https only.
- Paper sizes: A4, Letter and Legal. No custom sizes, page ranges, custom header or footer text, watermarks, encryption or password protection.
- Very long pages make large PDFs and can take longer than the default 45 s timeout.
- At the default 1 GB memory, keep `maxConcurrency` at 2 or below; image-heavy pages use the most memory. Allow about 400 MB per parallel page and raise memory for heavy sites or higher concurrency.
- Merging is limited to PDFs totalling 10% of the run's memory: about 102 MB at 1 GB, 409 MB at 4 GB. Over the limit, no merged file is made and `merge` isn't charged; you get an error row saying so, and every single PDF is still delivered.
- No raw-HTML input, custom cookies or headers, proxies or geo-targeting.
- For PNG or JPEG images of pages, use [Website Screenshot API](https://apify.com/altronite/website-screenshot).

### Related tools

- [Website Screenshot API](https://apify.com/altronite/website-screenshot): capture pages as PNG, JPEG or PDF.
- [Google Sheets Export & Import](https://apify.com/altronite/google-sheets-import-export): send any run's results to a Google Sheet.

### Support

Is a page not converting correctly? Open an issue on the **Issues** tab with the URL and your input.

# Actor input Schema

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

Public pages to convert, one per line; each becomes its own PDF. A missing scheme defaults to https://. Up to 500 per run. Example: \["https://example.com", "example.org/about"].

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

Page size of every PDF: A4 (210×297 mm), Letter (8.5×11 in) or Legal (8.5×14 in). Example: "Letter".

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

Print pages sideways (wide). Example: true for wide tables or dashboards.

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

Include background colours and images, as on screen. Example: false for plain, ink-saving pages.

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

Which of the site's styles to use: print (its print stylesheet, usually cleaner, menus often hidden) or screen (looks like the browser). Example: "screen".

## `margin` (type: `integer`):

Blank margin on all four sides, in millimetres, 0–50. With page numbers on, the bottom margin is at least 12 mm so the numbers fit. Example: 0 for edge-to-edge.

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

Add a centred "page / total" footer to every page (no other header text). Example: true for reports.

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

When the page counts as loaded before printing. Network idle is safest for heavy single-page apps but slower. Example: "networkidle".

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

Extra wait after load and before printing, e.g. for animations or late content. Max 30,000 ms. Example: 2000.

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

Hide common consent pop-ups with CSS before printing. Nothing is clicked or accepted. Example: false to keep the page untouched.

## `respectRobotsTxt` (type: `boolean`):

Skip URLs the site's robots.txt disallows (uncharged error row). Turn off only for pages you have the right to capture. Example: true.

## `timeoutSecs` (type: `integer`):

Limit for each step: page loading, then PDF printing, each separately. Load and print errors are retried once; a print that runs past the limit is not. Example: 90 for very long pages.

## `maxConcurrency` (type: `integer`):

Pages printed at the same time, 1–7 (default 2, sized for the default 1,024 MB memory). Allow about 400 MB of memory per parallel page; raise memory for heavy sites or higher concurrency. Example: 5 with 2,048 MB memory.

## `mergeIntoOne` (type: `boolean`):

Also join all delivered PDFs, in input order, into merged.pdf with one bookmark per source URL. Needs at least 2 delivered PDFs totalling at most 10% of the run memory (about 102 MB at 1 GB); charged once per merged file. The single PDFs are kept too. Example: true.

## Actor input object example

```json
{
  "urls": [
    "https://apify.com"
  ],
  "paperFormat": "A4",
  "landscape": false,
  "printBackground": true,
  "mediaType": "print",
  "margin": 10,
  "pageNumbers": false,
  "waitUntil": "load",
  "delayMs": 0,
  "hideCookieBanners": true,
  "respectRobotsTxt": true,
  "timeoutSecs": 45,
  "maxConcurrency": 2,
  "mergeIntoOne": false
}
```

# Actor output Schema

## `pdfs` (type: `string`):

Rows with PDF links, page counts and uncharged errors.

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

Keys of the PDFs (and merged.pdf) in the default 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 = {
    "urls": [
        "https://apify.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("altronite/url-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 = { "urls": ["https://apify.com"] }

# Run the Actor and wait for it to finish
run = client.actor("altronite/url-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 '{
  "urls": [
    "https://apify.com"
  ]
}' |
apify call altronite/url-to-pdf --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,altronite/url-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/LoKkWMLVywW9kXpA1/builds/Cz8z4NS2h5IOR8nhN/openapi.json
