# Website Screenshot & URL to PDF: Bulk Capture (`fernandosord/website-screenshot-pdf`) Actor

Capture full-page or viewport screenshots (PNG, JPEG, WebP) or PDFs of a list of URLs. Device presets, wait-for-selector, custom CSS, ad blocking and best-effort cookie banner hiding. Accepts the same input as apify/screenshot-url. Failed URLs are free.

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

## Pricing

from $3.00 / 1,000 screenshot or 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

Website Screenshot & URL to PDF takes a list of web addresses and saves each page as a full-page website screenshot (PNG, JPEG or WebP) or as a PDF. It works as a bulk screenshot API and URL to PDF converter: you get one result row per URL with a link to the file, its size, the page title and, for a failed URL, the reason.

It accepts the same input as [apify/screenshot-url](https://apify.com/apify/screenshot-url), so you can switch without changing your input or integration. This is an independent Actor. It is not made or endorsed by Apify.

### What data can Website Screenshot & URL to PDF extract?

Files go to the run's default key-value store. Each URL gets one row in the dataset.

| Field | Meaning |
|---|---|
| `url` | The URL you gave. |
| `finalUrl` | The URL after redirects. |
| `status` | `success`, `failed`, `site_blocked` (the site refused the browser), `not_allowed` (this Actor does not capture that URL) or `skipped` (your run's cost limit was reached). |
| `httpStatus` | The HTTP status of the page. |
| `format` and `device` | The file type and device preset used. |
| `width`, `height` | Image size in pixels. With pixel ratio 2 or 3, this is 2 or 3 times the CSS size. Empty for PDF. |
| `bytes` | File size. |
| `screenshotKey` | Record key in the run's key-value store. |
| `screenshotUrl` | Link to the file. It works without an API token. |
| `title` | The page title. |
| `truncated` | `true` if the page was taller than `maxHeight` and only the top part was captured. |
| `hiddenOverlays` | How many banners and pop-ups were hidden. |
| `warnings` | Things worth checking, such as a page that never stopped loading. |
| `capturedAt` | When the capture finished. |
| `error` | Why the URL failed. |

### How to use Website Screenshot & URL to PDF

1. Open the Actor on Apify and add your pages to the **URLs** field. You can also point it at a text file of links with `{ "requestsFromUrl": "https://example.com/my-urls.txt" }`. Exact duplicate URLs are captured once.
2. Choose a **Format**: PNG, JPEG, WebP or PDF. For HTML to PDF, pick PDF and set the paper size (A4 or Letter), orientation and whether backgrounds are printed.
3. Choose a **Device**: desktop (1920x1080), laptop (1366x768), iPhone (390x844 at 3x), iPad (820x1180 at 2x), or custom width, height and pixel ratio. Switch **Full page** off to capture only the visible screen.
4. Optional: wait for the network to go quiet, for a CSS selector to appear, or for a fixed delay. Turn on **Scroll to bottom** for lazy-loaded images. Hide elements with CSS selectors, or add your own CSS.
5. Click **Start**. When the run finishes, open the Output tab for a table with a preview of each page, or download the dataset as JSON, CSV or Excel.

Other options: cookie banner and pop-up hiding is on by default (best effort, nothing is clicked or accepted), and requests to 44 well-known ad and analytics services are blocked by default so heavy pages load faster. Both can be turned off. Up to 10 pages can be captured at once (`maxConcurrency`, 3 by default).

### How much does it cost?

The Actor uses pay-per-event pricing, so you pay for captures and not for compute time.

| Event | Price |
|---|---|
| Actor start | $0.002 per GB of memory, charged once per run (the default 2048 MB is 2 GB, so $0.004) |
| Screenshot or PDF | $0.003 per successful capture |

Failed, blocked, not-allowed and skipped URLs are free. 1,000 URLs that all succeed cost 1,000 x $0.003 + $0.004 = $3.004. If 50 of those fail or are blocked, you pay for 950 captures: $2.854. If you set a maximum cost for a run, the Actor stops starting new captures when the limit is reached and marks the remaining URLs as `skipped`. The Pricing tab always shows the current prices.

### Input example

```json
{
    "urls": [
        { "url": "https://example.com" },
        { "url": "https://apify.com" }
    ],
    "device": "desktop"
}
```

A mobile JPEG of the visible screen only, after a specific element has loaded:

```json
{
    "urls": [{ "url": "https://www.bbc.com/news" }],
    "format": "jpeg",
    "quality": 70,
    "device": "iphone",
    "fullPage": false,
    "waitForSelector": "main"
}
```

A PDF that looks like the page on screen:

```json
{
    "urls": [{ "url": "https://en.wikipedia.org/wiki/Screenshot" }],
    "format": "pdf",
    "pdfFormat": "A4",
    "pdfMediaType": "screen"
}
```

The main settings and their defaults:

| Field | Default | What it does |
|---|---|---|
| `format` | `png` | `png`, `jpeg`, `webp` or `pdf`. |
| `fullPage` | `true` | Capture the whole page. `false` captures the visible screen only. |
| `device` | `desktop` | `desktop`, `laptop`, `iphone`, `ipad` or `custom`. |
| `quality` | 80 | JPEG and WebP quality, 1 to 100. |
| `maxHeight` | 15000 | Full-page captures stop at this height in CSS pixels (30,000 at most), and the row is marked `truncated`. |
| `waitUntil` | `networkidle2` | `load`, `domcontentloaded`, `networkidle2` or `networkidle0`. |
| `waitForSelector` | | A CSS selector that must appear before capturing. |
| `delay` | 0 | Extra wait in milliseconds before capturing. |
| `scrollToBottom` | `false` | Scroll through the page first so lazy content loads. |
| `hideCookieBanners` | `true` | Hide consent banners and pop-ups. |
| `blockAdsAndTrackers` | `true` | Block common ad and analytics requests. |
| `pdfMediaType` | `print` | `print` uses the site's print stylesheet, `screen` looks like the page in a browser. |
| `navigationTimeoutSecs` | 45 | How long to wait for a page's HTML to load. |

The Input tab in Console describes every field.

### Output example

This is the first row of a real run on 2026-10-28 with the input above:

```json
{
    "url": "https://example.com",
    "finalUrl": "https://example.com/",
    "status": "success",
    "httpStatus": 200,
    "format": "png",
    "device": "desktop",
    "width": 1920,
    "height": 1080,
    "bytes": 119183,
    "screenshotKey": "example.com_b559c7edd3fb.png",
    "screenshotUrl": "https://api.apify.com/v2/key-value-stores/hVV9g82LLwXMHDw72/records/example.com_b559c7edd3fb.png?signature=1908V6IthxDL4S75a4r9J",
    "title": "Example Domain",
    "truncated": false,
    "hiddenOverlays": 0,
    "warnings": [],
    "capturedAt": "2026-10-28T21:58:19.614Z",
    "error": null
}
```

A blocked page gets a row like this, with no file and no charge. This one comes from a test run on 2026-10-21:

```json
{
    "url": "https://www.nytimes.com",
    "finalUrl": "https://www.nytimes.com/",
    "status": "site_blocked",
    "httpStatus": 403,
    "format": "png",
    "device": "desktop",
    "width": null,
    "height": null,
    "bytes": null,
    "screenshotKey": null,
    "screenshotUrl": null,
    "title": null,
    "truncated": false,
    "hiddenOverlays": null,
    "warnings": [],
    "capturedAt": "2026-10-21T15:39:10.491Z",
    "error": "The site returned HTTP 403. This usually means it requires a login or blocks automated browsers. This Actor does not try to get around that."
}
```

### Use cases

- Archive how a page looked on a given day, for compliance records or legal evidence.
- Turn a list of articles, invoices or documentation pages into PDFs with one run (HTML to PDF).
- Check a landing page on desktop, laptop, iPhone and iPad sizes before a release.
- Add visual thumbnails of websites to a directory, dashboard or report.
- Capture competitor pricing or product pages on a schedule and compare them over time.
- Produce screenshots for a QA report or a client deliverable.

### FAQ

#### Can I switch from apify/screenshot-url?

Yes. Your existing input works as it is. This input, written for apify/screenshot-url, runs here without changes:

```json
{
    "urls": [{ "url": "https://www.apify.com/" }],
    "format": "png",
    "waitUntil": "load",
    "viewportWidth": 1280,
    "scrollToBottom": true,
    "delayAfterScrolling": 2500,
    "proxy": { "useApifyProxy": true },
    "selectorsToHide": ""
}
```

Result rows have `url`, `screenshotUrl` and `screenshotKey`, the field names that Actor uses. When you set `viewportWidth` and no `device`, your width is used. Pages are captured full-length by default, so set `"fullPage": false` for the visible screen only. The `proxy` field is accepted but ignored. To switch an API call or integration, replace the Actor ID with `fernandosord/website-screenshot-pdf`.

#### Which pages cannot be captured?

Public pages only. There is no input for cookies, logins or passwords, so a page behind a login is captured as the login page. These sites are never captured because their terms forbid automated access: linkedin.com, facebook.com, instagram.com, x.com, twitter.com and tiktok.com, including their short-link domains. Local and private network addresses are refused too. These rows have the status `not_allowed` and are free.

#### Why was a site blocked?

Sites that block automated browsers are reported, not bypassed. The Actor runs from Apify's servers, uses an unmodified headless Chrome and does not rotate IP addresses or use proxies. In a test run, nytimes.com returned HTTP 403, and that row was marked `site_blocked` and not charged. Some sites show their own error page with a normal HTTP 200 status. The Actor cannot tell that apart from the real page, so it is captured and charged. Check the preview column.

#### How reliable is cookie banner hiding?

It is best effort. It hides the containers of common consent tools and fixed overlays that cover the page. Banners from other tools, and consent pages that replace the whole site, can still appear. Use `selectorsToHide` for ones it misses.

#### What are the size and speed limits?

Very tall pages are cut at `maxHeight` (15,000 CSS pixels by default, 30,000 at most). WebP images cannot be taller than 16,383 pixels. Simple pages take about a second. In the 2026-10-28 test run above, two pages took under 8 seconds including start-up. Large marketing homepages took roughly 20 seconds each in earlier testing with the default 2 GB of memory. Sections that only appear while you scroll can be blank, so turn on `scrollToBottom` for those pages.

#### Why do fonts look slightly different?

Pages are rendered by Chrome on Linux, so fonts can look slightly different from macOS or Windows.

#### How long are the files kept?

For your Apify plan's storage retention period. Download them if you need them longer.

#### Can I call it from the API, Make, Zapier or n8n?

Yes. Every Apify Actor has an API, and the Make, Zapier and n8n integrations can start it and read the dataset. To get the results in one call:

```bash
curl -X POST "https://api.apify.com/v2/acts/fernandosord~website-screenshot-pdf/run-sync-get-dataset-items?token=YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls":[{"url":"https://www.python.org"}],"format":"jpeg","fullPage":false}'
```

#### Can I schedule it or use it from an AI agent?

Yes. Use Apify's Schedules to run it hourly, daily or weekly. AI agents can call it through the Apify MCP server, which exposes Actors as tools.

#### Something went wrong. Where do I report it?

Open an issue on the Issues tab with the URL and the input you used.

### Other Actors by this developer

- [Website Change Monitor](https://apify.com/fernandosord/website-change-monitor): watch pages and get the exact lines or links that were added or removed.
- [Website Tech Stack Detector](https://apify.com/fernandosord/website-tech-stack-detector): find out which CMS, framework and tools a site uses.
- [Domain Profile, WHOIS and DNS](https://apify.com/fernandosord/domain-profile-whois-dns): registration, DNS and email setup for a domain.
- [Document to Markdown](https://apify.com/fernandosord/document-to-markdown): turn PDF, Word and other files into Markdown.
- [Open Places Leads](https://apify.com/fernandosord/open-places-leads): business lists by city or country from Overture Maps open data.

# Actor input Schema

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

Pages to capture. Each URL gives one result row and, if it succeeds, one file. Exact duplicates are captured once.

## `format` (type: `string`):

File type to save. PDF uses the paper settings below.

## `fullPage` (type: `boolean`):

Capture the whole scrollable page. Turn off to capture only the visible viewport. Ignored for PDF.

## `device` (type: `string`):

Screen size to emulate. Desktop is 1920x1080, laptop 1366x768, iPhone 390x844 at 3x, iPad 820x1180 at 2x. Choose Custom to use the width and height below. If left empty and a width is set, Custom is used; otherwise Desktop.

## `viewportWidth` (type: `integer`):

Viewport width in CSS pixels for the Custom device. Default 1280.

## `viewportHeight` (type: `integer`):

Viewport height in CSS pixels for the Custom device. Default 1080. With Full page on, this only affects layout, not the image height.

## `deviceScaleFactor` (type: `integer`):

Pixel density for the Custom device. 2 gives a retina-sharp image at twice the width and height.

## `quality` (type: `integer`):

Compression quality for JPEG and WebP, from 1 to 100. Ignored for PNG and PDF.

## `maxHeight` (type: `integer`):

Full-page captures stop at this height in CSS pixels, and the row is marked truncated. Very tall images are slow and large. WebP is also limited to 16,383 image pixels.

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

Page state to wait for before capturing. The wait stops 20 seconds after the HTML has loaded (or at the page load timeout, if shorter). A page that has not reached the state by then is captured as it is, and the row gets a warning.

## `waitForSelector` (type: `string`):

Optional CSS selector that must appear before capturing, for example #main or .product-grid. If it does not appear in time, the URL fails with an explanation and is not charged.

## `waitForSelectorTimeoutSecs` (type: `integer`):

How long to wait for the selector above.

## `delay` (type: `integer`):

Extra time to wait right before capturing, for animations or late content.

## `scrollToBottom` (type: `boolean`):

Scroll through the page before capturing so lazy-loaded images and sections load. Recommended for full-page captures of long pages.

## `delayAfterScrolling` (type: `integer`):

Time to wait after scrolling, unless the network-idle option below is on.

## `waitUntilNetworkIdleAfterScroll` (type: `boolean`):

After scrolling, wait until no requests are running for 0.5 s instead of using the fixed delay.

## `waitUntilNetworkIdleAfterScrollTimeout` (type: `integer`):

Longest wait for the network to go idle after scrolling.

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

Best effort. Hides banners from common consent tools and fixed overlays that cover the page. Nothing is clicked or accepted. Some banners and full-page consent walls will still show.

## `blockAdsAndTrackers` (type: `boolean`):

Block requests to 44 well-known ad and analytics services (Google Ads and Analytics, Meta Pixel, Hotjar, Criteo, Taboola and similar). Pages load faster and ad slots stay empty. Turn off if you need the ads in the capture.

## `selectorsToHide` (type: `string`):

CSS selectors to hide before capturing, one per line, for example .chat-widget or #promo-bar.

## `injectCss` (type: `string`):

CSS added to every page before capturing.

## `pdfFormat` (type: `string`):

Paper size when Format is PDF.

## `pdfPrintBackground` (type: `boolean`):

Include background colors and images in the PDF.

## `pdfLandscape` (type: `boolean`):

Use landscape orientation for the PDF.

## `pdfMediaType` (type: `string`):

Print uses the site's print stylesheet, which often hides menus and ads. Screen makes the PDF look like the page in a browser.

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

How many pages to capture in parallel. Higher values need more memory; with the default 2 GB, keep this at 3 or lower.

## `navigationTimeoutSecs` (type: `integer`):

How long to wait for the page's HTML to load. If it doesn't load in time, the URL is tried once more and then reported as failed.

## Actor input object example

```json
{
  "urls": [
    {
      "url": "https://example.com"
    },
    {
      "url": "https://apify.com"
    }
  ],
  "format": "png",
  "fullPage": true,
  "device": "desktop",
  "deviceScaleFactor": 1,
  "quality": 80,
  "maxHeight": 15000,
  "waitUntil": "networkidle2",
  "waitForSelectorTimeoutSecs": 15,
  "delay": 0,
  "scrollToBottom": false,
  "delayAfterScrolling": 2500,
  "waitUntilNetworkIdleAfterScroll": false,
  "waitUntilNetworkIdleAfterScrollTimeout": 30000,
  "hideCookieBanners": true,
  "blockAdsAndTrackers": true,
  "pdfFormat": "A4",
  "pdfPrintBackground": true,
  "pdfLandscape": false,
  "pdfMediaType": "print",
  "maxConcurrency": 3,
  "navigationTimeoutSecs": 45
}
```

# Actor output Schema

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

One row per URL with the file link, size, page title and, for failed URLs, the reason.

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

List of the image and PDF files saved 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 = {
    "urls": [
        {
            "url": "https://example.com"
        },
        {
            "url": "https://apify.com"
        }
    ],
    "device": "desktop"
};

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

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,fernandosord/website-screenshot-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/0iMthsIeRY8NLXCww/builds/cl6nfHoMqVAURhelR/openapi.json
