# Bulk Website Screenshots & PDFs from URLs (`plainsource/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/plainsource/website-screenshot-pdf.md
- **Developed by:** [Plainsource](https://apify.com/plainsource) (community)
- **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 & PDF Generator

Give this Actor a list of URLs and it saves a screenshot (PNG, JPEG or WebP) or a PDF of each page. You get one result row per URL with a link to the file, its size and the page title. If a URL fails, its row says why.

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 it can do

- Full-page or viewport-only captures.
- Device presets: desktop (1920x1080), laptop (1366x768), iPhone (390x844 at 3x) and iPad (820x1180 at 2x), or any custom width, height and pixel ratio.
- PNG, JPEG or WebP with a quality setting, or PDF on A4 or Letter paper, with or without backgrounds.
- Wait for the network to go quiet, for a CSS selector to appear, or for a fixed delay. Scroll the page first so lazy-loaded images load.
- Hide elements by CSS selector, or add your own CSS.
- Hide cookie banners and pop-ups (best effort, see Limits). Nothing is clicked or accepted.
- Block 44 well-known ad and analytics services, so heavy pages load faster. You can turn this off.
- Capture several pages at once (1 to 10).

### Input

The only required field is `urls`. Everything else has a default.

```json
{
    "urls": [
        { "url": "https://www.python.org" },
        { "url": "https://news.ycombinator.com" }
    ],
    "format": "png",
    "fullPage": true,
    "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"
}
```

You can also point `urls` at a text file of links with `{ "requestsFromUrl": "https://example.com/my-urls.txt" }`. Exact duplicate URLs are captured once.

#### Main settings

| Field | Default | What it does |
|---|---|---|
| `urls` | | Pages to capture. |
| `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`. |
| `viewportWidth`, `viewportHeight`, `deviceScaleFactor` | 1280, 1080, 1 | Size for the `custom` device. |
| `quality` | 80 | JPEG and WebP quality, 1 to 100. |
| `maxHeight` | 15000 | Full-page captures stop at this height in CSS pixels, 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. |
| `selectorsToHide` | | CSS selectors to hide, one per line. |
| `injectCss` | | Your own CSS, added to every page. |
| `pdfFormat`, `pdfPrintBackground`, `pdfLandscape`, `pdfMediaType` | A4, true, false, print | PDF options. |
| `maxConcurrency` | 3 | Pages captured at the same time. |
| `navigationTimeoutSecs` | 45 | How long to wait for a page's HTML to load. |

The Input tab in Console describes every field.

### Switching from apify/screenshot-url

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

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

What to know when you switch:

- Each result row has `url`, `screenshotUrl` and `screenshotKey`, the field names that Actor uses, so code that reads those fields keeps working. Rows also have more fields, listed under Output.
- When you set `viewportWidth` and no `device`, your width is used.
- Pages are captured full-length by default. Set `"fullPage": false` for the visible screen only.
- The `proxy` field is accepted but ignored. This Actor connects to sites directly (see Limits).
- To switch an API call or integration, replace the Actor ID with `plainsource/website-screenshot-pdf`.

### Output

Files go to the run's default key-value store. Each URL gets one row in the dataset. These rows come from a real run on 2026-10-21:

```json
{
    "url": "https://www.python.org",
    "finalUrl": "https://www.python.org/",
    "status": "success",
    "httpStatus": 200,
    "format": "png",
    "device": "desktop",
    "width": 1920,
    "height": 2632,
    "bytes": 698728,
    "screenshotKey": "www.python.org_1ab11d99cba1.png",
    "screenshotUrl": "https://api.apify.com/v2/key-value-stores/NRCntiiTp8c3OoV1g/records/www.python.org_1ab11d99cba1.png?signature=QF9it6M9DdRhQdBSgAJH",
    "title": "Welcome to Python.org",
    "truncated": false,
    "hiddenOverlays": 0,
    "warnings": [],
    "capturedAt": "2026-10-21T15:39:04.949Z",
    "error": null
}
```

```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."
}
```

| 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. |
| `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. |
| `error` | Why the URL failed. |

To get the results by API in one call:

```bash
curl -X POST "https://api.apify.com/v2/acts/plainsource~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}'
```

### Pricing

You pay a small fee when a run starts and a fixed price for each successful capture. Failed, blocked, not-allowed and skipped URLs are free. The current prices are on the Pricing tab. 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`.

### Limits

- Public pages only. There is no input for cookies, logins or passwords. A page behind a login is captured as the login page.
- 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 status. In one test run booking.com returned an "Oops!" page with HTTP 200. The Actor cannot tell that apart from the real page, so it was captured and charged. Check the preview column.
- Not captured at all: linkedin.com, facebook.com, instagram.com, x.com, twitter.com and tiktok.com (including their short-link domains), because their terms forbid automated access. Local and private network addresses are refused too.
- Cookie banner hiding 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.
- Very tall pages are cut at `maxHeight` (15,000 CSS pixels by default, 30,000 at most). WebP images can't be taller than 16,383 pixels.
- Scroll-triggered content. Sections that only appear while you scroll can be blank. Turn on `scrollToBottom` for those pages.
- Speed. Simple pages take about a second. Heavy marketing and news homepages took 15 to 45 seconds each in testing with the default 2 GB of memory.
- Fonts. Pages are rendered by Chrome on Linux, so fonts can look slightly different from macOS or Windows.
- Files are kept for your Apify plan's storage retention period. Download them if you need them longer.

### Questions and problems

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

# 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("plainsource/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("plainsource/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 plainsource/website-screenshot-pdf --silent --output-dataset

```

## MCP server setup

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