URL to PDF: Save Web Pages as PDF Documents avatar

URL to PDF: Save Web Pages as PDF Documents

Pricing

from $7.00 / 1,000 pdf saveds

Go to Apify Store
URL to PDF: Save Web Pages as PDF Documents

URL to PDF: Save Web Pages as PDF Documents

Save any list of URLs as PDF in real Chromium: A4, Letter or Legal, landscape, margins, print CSS, page numbers, public links. Blocks are free. $7 per 1K PDFs of up to 20 pages.

Pricing

from $7.00 / 1,000 pdf saveds

Rating

0.0

(0)

Developer

Kaizoku Data

Kaizoku Data

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

10 days ago

Last modified

Security

Limited permissions

Share

Save a list of web pages as PDF files, printed by a real Chromium browser. Choose A4, Letter or Legal, portrait or landscape, your margins and scale, the page's print or screen styles, and optional page numbers with the URL and date. Each PDF is stored in the run's key-value store with a public link. Each row lists the final URL, HTTP status, title, page count, file size and load time. No proxy, no key.

Price: $7 per 1,000 PDFs ($0.007 each, up to 20 PDF pages included). Longer documents cost $0.0001 per page after the 20th ($0.10 per 1,000 pages). Apify's default Actor-start event applies. Failed pages, blocks, CAPTCHA or bot-challenge pages, timeouts and refused URLs are free.

  • Paper and layout: A4, Letter or Legal; portrait or landscape; margins from 0 to 50 mm; scale from 10% to 200%; backgrounds on or off.
  • Print or screen styles: print uses the site's own print stylesheet, as when you press Print in a browser. screen keeps the page as it looks on screen, cut into pages.
  • Page numbers on request: a fixed header (date and URL) and footer ("Page X of Y"). No custom HTML is accepted, so nothing can be injected.
  • Honest output: the page count, paper size, PDF version and file size are read back from the saved file. A page longer than Max pages is cut there and marked truncated.
  • Quick start: press Start. The prefilled input saves https://crawlee.dev/ (a site run by Apify) as an A4 PDF of 4 pages. It takes under a minute (about 15–20 seconds) and costs $0.007.

Safe and polite by design. robots.txt is read and obeyed for every page and every redirect hop. The browser's User-Agent says it is an automated Chrome and names this Actor. Every request a page makes goes through a guard that refuses private, internal and cloud-metadata addresses. Blocks and challenges become free rows: they are never retried or worked around.

What it does

For each URL:

  1. Guard. The URL must be http(s). Social-media, video and Google hosts are refused before any DNS lookup (platform), and so are private, loopback and link-local addresses (ssrf).
  2. robots.txt. The robots.txt of the page's origin is fetched once per run with the product token url-to-pdf-actor and obeyed. A Crawl-delay is honoured in full. Pages on one host are loaded at most once per second. The page's own images, scripts, styles and fonts load as in any browser and pass the address guard. Preloading is switched off, so a page cannot make the browser fetch other pages it was not sent to. Speculation rules and <link rel=prefetch|prerender> are removed before the browser acts on them, whether they are in the HTML, added by scripts or sent as headers. Any remaining prefetch request is refused.
  3. Fresh browser context. Each URL gets its own isolated context (no cookies, cache, storage or service workers shared with other URLs). One Chromium is reused for the whole run.
  4. Load. The page is loaded, with the screen styles switched on first if you asked for them. Every redirect hop, and every later navigation of the page itself, is checked against the same rules (platform list, addresses, robots.txt) before it is requested. A refusal (401, 403, 429, 503, 999), a bot challenge or CAPTCHA wall, or an HTTP error gives a free row without a PDF.
  5. Wait. The Actor waits for your chosen load state, then your delay. With Hide cookie banners on, it adds the consent stylesheet. It then scrolls down the page once (at most 3 s), so images that load on scroll start loading. Images marked loading="lazy" are loaded by the browser when it prints.
  6. Print. Chromium prints the page with your paper, margins, scale and background settings, pages 1 to Max pages. The PDF is checked before it is saved: it must be readable and at most 50 MB, and its page count and paper size are read from the file. It is saved to the key-value store as application/pdf.
  7. Row. The row is written with the public link. A row is charged only after both the PDF and the row are saved.

Input

FieldWhat it does
URLs (urls)Pages to save, one per line, up to 200 per run. example.org becomes https://example.org/. Duplicates are saved once.
Paper format (format)A4 (default, 210 × 297 mm), Letter (8.5 × 11 in) or Legal (8.5 × 14 in).
Landscape (landscape)Default off. On: the paper is turned sideways.
Margins (mm) (margins)0–50, default 10, on all four sides. With the header and footer on, top and bottom are at least 15 mm.
Print backgrounds (printBackground)Default on: background colours and images are printed, so the PDF looks like the page. Off: a white background, as in a browser's default print.
Scale (%) (scale)10–200, default 100. Below 100 fits more on each page.
CSS media type (mediaType)print (default): the site's print styles. screen: the on-screen look.
Header and footer (headerFooter)Default off. On: date and time (UTC) and the page's URL at the top of every page; "Page X of Y" at the bottom. The text is fixed.
Max pages per PDF (maxPages)1–300, default 100. A longer page is cut and marked truncated. Pages after the 20th cost $0.0001 each.
Wait until (waitUntil)load (default), domcontentloaded or networkidle (the load event, then up to 10 s more until the network is quiet for 0.5 s).
Extra delay (ms) (delayMs)0–10,000, default 0.
Hide cookie banners (hideCookieBanners)Default on. Hides the containers of about 20 common consent tools (OneTrust, Cookiebot, Didomi, Quantcast, TrustArc, Usercentrics, Osano, Termly, Iubenda, Complianz, CookieYes, Sourcepoint, Axeptio, Borlabs, consentmanager and others) with one stylesheet.

Example inputs

1. A list of pages as A4 PDFs with the default settings (archiving, sharing, offline reading):

{
"urls": ["https://crawlee.dev/", "https://www.python.org/", "https://go.dev/"]
}

2. US Letter with page numbers, the date and the URL on every page (reports, evidence of what a page said):

{
"urls": ["https://books.toscrape.com/", "https://quotes.toscrape.com/"],
"format": "Letter",
"headerFooter": true,
"margins": 12
}

3. A wide page as it looks on screen, in landscape, at most 10 pages:

{
"urls": ["https://books.toscrape.com/catalogue/category/books/travel_2/index.html"],
"mediaType": "screen",
"landscape": true,
"scale": 80,
"maxPages": 10
}

Output

You get one item per URL (JSON, CSV, Excel or XML). The Overview view links each PDF, and the Free rows and problems view lists what was not saved and why. Example from a real run:

{
"url": "https://crawlee.dev/",
"requestedUrl": "https://crawlee.dev/",
"finalUrl": "https://crawlee.dev/",
"httpStatus": 200,
"title": "Crawlee · Build reliable crawlers. Fast.",
"status": "ok",
"billable": true,
"chargedEvent": "pdf",
"pdfKey": "pdf-crawlee.dev-151c821820-5ad554f4.pdf",
"pdfUrl": "https://api.apify.com/v2/key-value-stores/<store id>/records/pdf-crawlee.dev-151c821820-5ad554f4.pdf",
"pages": 4,
"extraPages": 0,
"truncated": false,
"bytes": 698648,
"sha256": "3475837f…",
"pdfVersion": "1.4",
"format": "A4",
"landscape": false,
"pageWidthPt": 595.92,
"pageHeightPt": 841.92,
"mediaType": "print",
"loadMs": 5797,
"loadState": "load",
"renderMs": 11996,
"pageBytes": 871206,
"pageRequests": 18,
"blockedRequests": 6,
"blockedHosts": ["www.googletagmanager.com (platform)", "analytics.tiktok.com (platform)", "www.redditstatic.com (platform)", "analytics.twitter.com (platform)", "snap.licdn.com (platform)"],
"note": null,
"errorCode": null,
"message": null,
"capturedAt": "2026-10-26T21:23:51.713Z",
"urlKey": "https://crawlee.dev/"
}

(Times are from a run limited to a quarter of a CPU core, the platform's share at 1 GB.)

FieldMeaning
status, billable, chargedEventok = PDF saved and charged (pdf); error = free row, see errorCode and message.
pdfUrl, pdfKeyPublic link to the PDF and its key in the run's default key-value store.
pages, extraPages, truncatedPages in the saved PDF; pages after the 20th (charged as extra-page); true when the page was longer than Max pages (or than the run's maximum charge pays for) and the PDF stops there.
bytes, sha256, pdfVersion, pageWidthPt, pageHeightPtRead from the saved file. Sizes are in points (1/72 inch): A4 595.92 × 841.92, Letter 612 × 792, Legal 612 × 1008, swapped in landscape.
finalUrl, httpStatus, titleThe page after redirects, its status and its title (at most 300 characters).
loadMs, loadState, renderMsTime to the load state that was reached, which state, and the total browser time for this URL.
pageBytes, pageRequestsWhat the page downloaded through the Actor's guard.
blockedRequests, blockedHostsRequests of the page that were refused, with up to 10 hosts and reasons (ssrf, platform, port). The page is still printed.
noteFor example "The load event did not fire within 30 s; printed after DOMContentLoaded", or "The page is longer than 100 PDF pages; the PDF stops at page 100".

Error codes (all free)

CodeMeaning
invalid-inputNot an http(s) URL, embedded credentials, or longer than 2,048 characters (file:, chrome:, javascript:, data: and the like are refused).
platformA social-media, video or Google host (YouTube, TikTok, Instagram, Facebook, X, Vimeo, SoundCloud, Spotify, Twitch, Reddit, LinkedIn, Google domains), also when reached by a redirect. Never contacted.
ssrfThe host is, or resolves to, a private, loopback, link-local, carrier-grade NAT or reserved address, also after a redirect.
robots-blockedrobots.txt disallows the page, or a redirect target, for this Actor. Not requested.
robots-unavailablerobots.txt answered 5xx or 429, or could not be read (tried twice). The page is not loaded without its rules.
slow-hostrobots.txt asks for a Crawl-delay over 30 s, or the run's 600 s of Crawl-delay waiting is used up. The delay is never shortened.
refusedThe page answered 401, 403, 429, 503 or 999. Not retried, and no other method is tried.
challengeA bot challenge or CAPTCHA wall (a Cloudflare challenge, hCaptcha or reCAPTCHA wall, PerimeterX, DataDome, or a bare "Access denied" page). Never solved or worked around.
host-skippedThe host refused or challenged 2 pages in this run, so it is not asked again.
httpAnother HTTP error (for example 404 or 500). No PDF is made of an error page.
dns, network, tlsThe name does not resolve, the connection failed, or the HTTPS certificate was not accepted.
timeoutNo HTML within 30 s, printing took over 60 s, or the page used up its time budget.
downloadThe URL starts a file download instead of showing a page. Downloads are refused. (To fetch existing PDF files, use a file downloader.)
too-largeThe page downloaded more than 50 MB or made more than 1,500 requests, or the PDF is larger than 50 MB.
redirects, invalid-urlMore than 5 redirects (HTTP or page-initiated), or a redirect to an unusable URL or port.
page-crashed, browser-crashedThe tab or the browser crashed, usually because the page needs more memory. The next URL gets a fresh browser.
navigation, store-failed, internalAnother loading error, the PDF could not be saved, or an unexpected error in this Actor.

RUN_SUMMARY (key-value store record)

This record holds:

  • charged and free rows, and the charged events (pdf, extra-page);
  • PDFs, PDF pages, PDF bytes, and PDFs cut at Max pages;
  • browser time (browserSecs, browserSecsUnpaid) and time per step (phaseMs);
  • bytes downloaded by pages, requests, and refused requests;
  • browser launches and crashes;
  • errors by code;
  • stoppedBy: finished, max-charge, unpaid-work-cap, time-limit, migrating, aborted, browser-crashed, browser-launch or error.

Pricing

Pay per event:

  • pdf = $0.007: one PDF saved, up to 20 PDF pages included.
  • extra-page = $0.0001: each page after the 20th of a saved PDF. Most web pages print to fewer than 20 pages; long documentation pages, specifications and articles can print to 50–100 or more, and they take more browser work.

These are free:

  • URLs whose page was blocked or showed a challenge;
  • URLs that timed out or failed;
  • URLs that were refused (robots.txt, platform, private addresses) or invalid.

There is no start fee of our own; Apify's default Actor-start event applies.

PDFs savedCost
1 (the prefilled example, 4 pages)$0.007
100 ordinary pages (20 PDF pages or fewer each)$0.70
1 long document of 100 pages (the default Max pages)$0.007 + 80 × $0.0001 = $0.015
200 ordinary pages (the per-run maximum)$1.40
10,000 ordinary pages (50 runs)$70

Set a maximum charge per run in the run options. The Actor stops cleanly before the next URL it could not pay for: that URL is not loaded and no PDF is saved. A long document is printed only up to the pages the remaining budget pays for. It is cut there, marked truncated, and the note says why. To protect you from paying for compute on bad lists, the run also stops when:

  • free rows reach 50 and outnumber the charged ones;
  • browser time spent on free rows passes 300 s plus 2 s per saved PDF;
  • 300 MB were downloaded for free rows.

Memory. The default and minimum is 1 GB. More memory makes each page faster, but the price per PDF is the same.

Use it via API, n8n or Make

Get an API token in Apify Console under Settings, API & Integrations, and keep it in an environment variable (APIFY_TOKEN below). Never paste it into shared code.

cURL (starts a run, waits and returns the rows; the synchronous endpoint waits up to 5 minutes, so use it for lists of up to about 20 URLs):

curl -X POST "https://api.apify.com/v2/acts/ataism~url-to-pdf/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"urls": ["https://crawlee.dev/"], "format": "Letter"}'

Python (pip install "apify-client>=3"):

import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("ataism/url-to-pdf").call(run_input={
"urls": ["https://crawlee.dev/", "https://www.python.org/"],
"format": "A4",
"headerFooter": True,
})
for row in client.dataset(run.default_dataset_id).iterate_items():
print(row["url"], row["status"], row["pages"], row["pdfUrl"], row["errorCode"])

(With apify-client versions before 3.0, call() returns a dict: use run["defaultDatasetId"].)

JavaScript (npm install apify-client):

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('ataism/url-to-pdf').call({
urls: ['https://crawlee.dev/'],
landscape: true,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const row of items) console.log(row.url, row.pages, row.bytes, row.pdfUrl);

n8n:

  1. Add the Apify node, operation Run an Actor and get dataset, Actor ataism/url-to-pdf, input {"urls": ["https://your-site.example/"]}.
  2. Use pdfUrl in the next node, for example an HTTP Request node that downloads the file to attach it to an email or upload it to Google Drive.

Make:

  1. Add the Apify module Run an Actor, choose ataism/url-to-pdf, put your URLs in the input JSON and set Run synchronously to Yes.
  2. Add the Apify module Get Dataset Items and map the run's default dataset ID (one bundle per URL).
  3. Send url, pdfUrl, pages and status to Google Sheets, Airtable or your document store.

Privacy, rights and terms

  • You are responsible for having the rights to save the pages you list as PDF, and to use the PDFs. A PDF copies the page's text, images, logos and any personal data shown on it. Save only pages you are allowed to save: your own sites, sites whose terms allow it, or uses your law permits (for example archiving your own pages or keeping a record of public terms you agreed to). Nothing here is legal advice.
  • No logins, no paywalls, no consent clicking. The Actor sends no cookies or credentials of yours, and it never clicks a button. The cookie-banner option only hides banners with CSS, so a page that shows content only after consent is printed as it looks without consent.
  • Public links. PDFs are saved in your run's key-value store. Their links can be opened by anyone who has them (like any Apify record URL). Delete the store, or keep the links private, if that matters to you.
  • What the output contains: the page's URL, status, title, timings and the PDF. With the header and footer on, the PDF also shows the date, time and URL. The Actor adds nothing else about the page, but the PDF shows whatever the page shows.
  • Polite access. robots.txt is obeyed for every page and every redirect. The browser's User-Agent ends with url-to-pdf-actor/0.1 (+https://apify.com/ataism/url-to-pdf) and still says HeadlessChrome. Blocks and challenges are reported, never worked around. No proxies are used. Social-media, video and Google sites are refused.

Security

The browser loads pages from the open web, so the Actor treats each page as untrusted:

  • Every request of the page is guarded. The browser has no direct network access. Each request goes through a small guard proxy inside the run: the page, every redirect, iframes, scripts, images, fonts, fetch/XHR, WebSockets and preconnects. The guard resolves the host name itself and refuses private, loopback, link-local and reserved addresses (IPv4 and IPv6, including IPv4-mapped, NAT64 and 6to4). It then connects to exactly the address it checked, so a name that changes its answer (DNS rebinding) gains nothing. Chrome's own DNS is switched off. WebRTC is limited to the proxy, and QUIC (HTTP/3) is off. Only ports 80, 443, 8080, 8443 and the page's own port are allowed. HTTPS is not intercepted: certificates are checked by Chrome as usual.
  • Not just the first request. Redirects and page-initiated navigations are paused and checked before they are sent. If the page navigates away while it is being printed and that navigation is refused or fails, the row is free. Chrome's error page is never printed.
  • Nothing from the input runs or is inserted. The only scripts the Actor runs in a page are its own fixed functions: reading the title, the challenge check, the scroll, the banner stylesheet and the preloading guard (it runs in a separate script world the page cannot reach). The header and footer are fixed templates; Chrome fills in the date, URL and page numbers as text.
  • Nothing in the PDF can act. Chrome's PDFs contain no scripts, forms, attachments or open actions. Links stay clickable when they point to web or mail addresses (http, https, mailto, tel). Links to javascript:, file:, data: and other targets are disabled in the saved file, and the row's note says how many. The page count and paper size are read from the PDF's own page tree, so text on the page (such as its title) cannot change them or the price.
  • No downloads, no service workers, no popups, no preloading, no dialogs. Downloads are refused and service workers are blocked. Popups and new tabs are never opened, so nothing they would load is requested. The page's prefetch and prerender requests are removed or refused; every page the browser loads goes through the check above. Dialogs are dismissed. Video and audio are not loaded (poster images still show).
  • No secrets in the browser. Chromium starts with a short list of environment variables. The run's API token is not among them.
  • Bounded. Each page has a hard time budget (at most about 110 s plus your delay), 50 MB and 1,500 requests, and the PDF itself at most 50 MB. A crashed tab or browser gives a free row, and the next URL gets a fresh one.

Limits and honest caveats

  • Max pages: at most 300 pages per PDF (default 100). Longer pages are cut and marked truncated.
  • Print styles decide the look. With print (the default), many sites hide menus, ads and some images when printed, and some show link addresses after links. Use screen to keep the on-screen look. The paper size is always the one you chose; a page's own @page size is ignored. Print styles also decide the page count: a page whose print style hides its content gives a blank PDF, and one with very large print text gives many pages (charged, up to Max pages). Check a sample first, and lower Max pages if you are unsure.
  • Pages that scroll inside a container (some web apps keep the page at the window height and scroll an inner element) print only the part that fits in the window.
  • Lazy content: images marked loading="lazy" are loaded for printing. Images that a script loads on scroll start loading during the Actor's one scroll down the page (at most 3 s). Content that loads only on clicks or infinite scroll is not loaded.
  • Bot protection: many large sites refuse automated browsers. Those rows are free (refused or challenge); the Actor will not try to get around them. Challenge detection is best-effort: a challenge served in an unusual way can still be printed and charged.
  • Cookie banners: only the listed tools are hidden. A banner from another tool stays visible.
  • Google and social content inside pages: pages may embed resources from Google, YouTube, Facebook, X and the like. Those are not loaded, so embedded videos, maps, posts and ad slots show as empty boxes. Web fonts from fonts.googleapis.com and fonts.gstatic.com are the one exception: they are loaded so text prints in the right font.
  • Preloading inside shadow DOM: speculation rules that a page places inside a shadow root (a web component's own DOM) are outside the preloading guard and could still make the browser prefetch a page. We know of no site that does this. Such a prefetch is never printed or charged, because every navigation is checked against robots.txt before it is sent.
  • Header and footer: fixed layout and text (date and time in UTC, US format; URL; "Page X of Y"). In a cut PDF, "of Y" counts the whole page.
  • Load time: if the load event does not arrive within 30 s, the page is printed as it is at that moment (charged, with a note). If no HTML arrives within 30 s, the row is a free timeout.
  • PDF files only from web pages: a URL that serves a file (a PDF, ZIP or the like) is a free download row.
  • Per run: 200 URLs and 55 minutes (a run that reaches the time limit stops cleanly with stoppedBy: time-limit; split long lists); one page at a time, in one browser.

FAQ

Why is my PDF missing the menu or the background? That is the site's print stylesheet (CSS media print). Set CSS media type to screen, and keep Print backgrounds on.

Why is my PDF showing an "Access denied" page? If the Actor recognised a block, the row is a free refused or challenge row. If the site served a normal-looking page that is empty until a login or consent, that is what the page looks like to a visitor without them.

Can I save pages behind a login? No. The Actor sends no cookies or credentials and never fills in forms.

Can I convert my own HTML or merge PDFs? Not with this Actor. It prints public web pages by URL, one PDF per URL.

Open-source components

  • apify SDK 3.7.2 (Apache-2.0).
  • playwright-core 1.64.1 (Apache-2.0), with the Chromium build that ships in Apify's actor-node-playwright-chrome image.

Everything else (the guard proxy, robots.txt, the URL guard, challenge detection and the PDF reader) is written in this repository without further dependencies.