Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Use a real browser session to sign in, confirm the protected page has rendered, then call the browser’s PDF function. For repeatable jobs, Playwright or Puppeteer is more dependable than a print-dialog script because it can restore authentication state, wait for application data, and set paper, margin, background, and page-range options. A normal browser’s Print command remains suitable for a one-off export when you can already view the page.

Choose the right workflow

Approach Best fit What it provides Important limitation
Browser Print dialog One-time export while you are signed in Uses the session and print preview already open in your browser Manual, difficult to repeat, and browser-specific; there is no universal workflow for every site
Playwright Repeatable automation and saved login state storageState for supported cookies, local storage, IndexedDB and passkeys; Page.pdf() output controls The documented storageState API does not persist session storage; PDF generation depends on the selected browser path. Authentication guide and Page API
Puppeteer JavaScript automation built around its Page API Page.pdf(); its documented example waits for network idle and fonts are awaited by default You must establish authentication in the browser context. Puppeteer PDF guide
Playwright MCP PDF export An MCP client already controlling a browser Saves the current page as a PDF PDF export is Chromium-only and the page must already be in the desired authenticated state. MCP PDF tool documentation

Pick based on how authentication works, whether the export is once or recurring, your runtime, browser support and the layout controls you need. The available documentation does not establish a speed or quality ranking.

One-off export with a browser

  1. Sign in through the website’s normal login flow.
  2. Open the protected page and expand any sections that must appear. Wait until the actual text, tables and images are visible.
  3. Use the browser’s Print command, choose “Save to PDF,” select paper size, orientation, margins and background graphics, then save.
  4. Open the PDF and check page breaks, images, fonts, links and all content that requires authentication.

A successful navigation event alone does not prove that a client-rendered page, lazy image or API request has finished. If the result is blank or incomplete, use automation with an explicit readiness check.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Automate with Playwright (Node.js)

Install and create a private authentication state

Install Playwright and its browser once:

npm init -y
npm install playwright
npx playwright install chromium

Run a small setup script that performs the site’s normal login, then saves the supported state. Replace selectors and URLs with the site’s real ones.

// auth-setup.mjs
import { chromium } from 'playwright';

const browser = await chromium.launch({ headless: false });
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://example.com/login', { waitUntil: 'domcontentloaded' });
await page.fill('input[name="email"]', process.env.SITE_EMAIL);
await page.fill('input[name="password"]', process.env.SITE_PASSWORD);
await page.click('button[type="submit"]');
await page.waitForURL('**/dashboard');
await context.storageState({ path: 'playwright/.auth/user.json' });
await browser.close();

Keep playwright/.auth/user.json private, short-lived and out of source control. It can contain credentials or tokens. A typical .gitignore entry is:

playwright/.auth/

Playwright documents this state-reuse workflow in its authentication documentation. Sites can also depend on session storage or application-specific state; the documented API does not save session storage, so test the restored context against the real page.

Render, verify and write the PDF

// export-pdf.mjs
import { chromium } from 'playwright';

const browser = await chromium.launch();
const context = await browser.newContext({
  storageState: 'playwright/.auth/user.json'
});
const page = await context.newPage();
await page.goto('https://example.com/account/report', {
  waitUntil: 'domcontentloaded'
});

// Use a selector that proves the protected data, not merely page navigation.
await page.locator('[data-report-ready]').waitFor({ state: 'visible', timeout: 30000 });
await page.waitForLoadState('networkidle');

const title = await page.locator('h1').textContent();
if (!title?.trim()) throw new Error('Authenticated report title is missing');

// Print CSS is the default. Set the options deliberately for your document.
await page.emulateMedia({ media: 'print' });
await page.pdf({
  path: 'report.pdf',
  format: 'A4',
  printBackground: true,
  margin: { top: '18mm', right: '14mm', bottom: '18mm', left: '14mm' }
});

await browser.close();

Run it with node export-pdf.mjs. Page.pdf() supports paper formats or explicit dimensions, margins, page ranges, scaling, landscape output, headers and footers, tagged output and background printing; see the Page API for the options available in your installed version. If the screen design is preferable, call await page.emulateMedia({ media: 'screen' }) before generating the file. Inspect the actual PDF after every layout change.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Log in interactively, then reuse the state

For multi-factor authentication, SSO or a passkey, run the setup browser headed (headless: false), complete the challenge yourself, and save state only after the target page is accessible. Do not attempt to bypass a challenge or automate a site’s security controls. For scheduled jobs, refresh the state through the site’s permitted login process when its cookies or tokens expire.

Automate with Python Playwright

The same sequence works in Python. Install the package and browser:

pip install playwright
playwright install chromium

Authenticate once:

# auth_setup.py
import os
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    context = browser.new_context()
    page = context.new_page()
    page.goto("https://example.com/login", wait_until="domcontentloaded")
    page.fill('input[name="email"]', os.environ["SITE_EMAIL"])
    page.fill('input[name="password"]', os.environ["SITE_PASSWORD"])
    page.click('button[type="submit"]')
    page.wait_for_url("**/dashboard")
    context.storage_state(path="playwright/.auth/user.json")
    browser.close()

Export after restoring it:

# export_pdf.py
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    context = browser.new_context(storage_state="playwright/.auth/user.json")
    page = context.new_page()
    page.goto("https://example.com/account/report", wait_until="domcontentloaded")
    page.locator("[data-report-ready]").wait_for(state="visible", timeout=30000)
    page.wait_for_load_state("networkidle")
    if not page.locator("h1").inner_text().strip():
        raise RuntimeError("Authenticated content is not present")
    page.emulate_media(media="print")
    page.pdf(
        path="report.pdf",
        format="A4",
        print_background=True,
        margin={"top": "18mm", "right": "14mm", "bottom": "18mm", "left": "14mm"},
    )
    browser.close()

Use a selector tied to the page’s real data. A generic body selector can become visible before the protected API response arrives.

Puppeteer alternative (Node.js)

Puppeteer’s flow is similar: create a browser context, authenticate, navigate, wait for the page’s data and call page.pdf(). Its guide documents waiting for a network-idle condition and says the PDF call waits for fonts by default.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com/login', { waitUntil: 'domcontentloaded' });
await page.type('input[name="email"]', process.env.SITE_EMAIL);
await page.type('input[name="password"]', process.env.SITE_PASSWORD);
await Promise.all([
  page.click('button[type="submit"]'),
  page.waitForNavigation({ waitUntil: 'networkidle2' })
]);
await page.goto('https://example.com/account/report', { waitUntil: 'networkidle2' });
await page.waitForSelector('[data-report-ready]', { visible: true });
await page.pdf({
  path: 'report.pdf',
  format: 'A4',
  printBackground: true,
  margin: { top: '18mm', right: '14mm', bottom: '18mm', left: '14mm' }
});
await browser.close();

Replace the example selectors and URLs. Network idle and font readiness are useful signals, not proof that every site’s asynchronous content has finished.

Authentication state: what can go wrong

Cookies and local storage

Playwright’s saved state can reuse supported cookies, local storage, IndexedDB and passkeys. Verify access after restoring it; a cookie may be scoped to a different domain, path or browser context.

Session storage

The documented storageState API does not persist session storage. If the application stores its login marker there, initialize that state using an app-appropriate mechanism and then test the protected URL before export.

SSO, MFA and expiring tokens

Interactive identity-provider redirects and one-time challenges may require a headed setup run. A saved file can become invalid when its session expires. Reauthenticate through the normal process rather than trying to circumvent the provider.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Authorization headers or cookies supplied by an API

If your application legitimately provides a short-lived token, create the context with the required headers or cookies and keep those values in environment variables or a secret manager. Never place live tokens in source control, logs or a shared PDF job queue.

PDF layout controls that matter

  • Print versus screen CSS: Playwright generates PDFs with print media by default. Emulate screen media when the on-screen design is the intended output.
  • Backgrounds: Enable printed backgrounds when cards, charts or color-coded sections otherwise disappear.
  • Paper and orientation: Choose A4, Letter, landscape or explicit width and height to match the audience and source layout.
  • Margins and scale: Increase margins for readable headers and reduce scale only after checking that text remains legible.
  • Page ranges: Export selected ranges when a long report contains navigation or unrelated sections.
  • Print CSS: Add @media print rules to avoid clipping, hide controls and keep headings with their content. Prefer CSS page-break properties over arbitrary delays.
  • Fonts and images: Wait for the page’s actual assets and inspect the PDF; a loaded DOM does not guarantee that lazy images or web fonts are ready.

Verification checklist

  1. Confirm the URL is the protected page, not a login redirect.
  2. Assert a unique authenticated heading, row count or status marker.
  3. Wait for the application’s data and lazy assets, using a specific selector or app signal.
  4. Generate the PDF with deliberate paper, margin, background and media settings.
  5. Open the file and check every page for clipping, missing images, incorrect colors, fonts and page breaks.
  6. For records or compliance, confirm that the site permits retaining and distributing the content. The cited technical documentation does not establish any site’s terms or legal permissions.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshooting

The PDF shows a login page

The saved state is expired, incomplete or scoped incorrectly. Reauthenticate, save a new state, and log the restored page URL and a protected selector before calling pdf().

The PDF is blank or missing rows

Navigation finished before client-rendered data arrived. Wait for the data-specific selector or application-ready signal; use network-idle only as an additional signal. Check that the page’s API requests are not failing.

Session-dependent pages still reject the context

Check whether the app depends on session storage, an IndexedDB record, a passkey or a domain-specific cookie. Playwright’s documented state file does not include session storage, so initialize that part separately.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Colors or backgrounds disappeared

PDF output uses print media by default. Enable printBackground, inspect print CSS, or emulate screen media before export.

Content is clipped or page breaks look wrong

Adjust paper format, orientation, margins or scale; then inspect the resulting file. Add print-specific CSS for tables, long code blocks and headings. Do not assume a setting that works for one viewport works for another.

MCP PDF export fails

Confirm the PDF capability is enabled, the page is already authenticated and Chromium is the selected browser. Playwright’s MCP documentation identifies Chromium as the PDF-generating browser.

Or skip the browser setup

ScreenshotNeo can capture a page or create a PDF through one HTTP request. For a protected page, provide the permitted cookies, custom headers or Authorization value in the request configuration; keep those credentials server-side and short-lived. The API supports PDF paper size, margins, landscape mode and page ranges, plus waits for a selector, delay or network idle. It can also click an element, hide selectors, load lazy images and block unwanted requests.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Basic request (replace the URL and add your authenticated request options in your own integration):

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

See the ScreenshotNeo API documentation for PDF parameters and authenticated request options. Before capture it accepts cookie/consent banners and removes more than 60 known consent platforms, newsletter popups and chat widgets; each step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients. The Free plan includes 1,000 shots each month without a card; paid plans start at $5 for 3,000 shots, and every feature is included on every plan. Create a free ScreenshotNeo account.

Cost, reliability and operational notes

  • Browser automation costs compute time and requires maintenance when login forms, selectors or print CSS change.
  • Keep authentication files and tokens out of logs, artifacts and source control; restrict file permissions and delete expired state.
  • Use bounded timeouts and fail clearly when the protected selector never appears. A silently generated login-page PDF is worse than a failed job.
  • For recurring exports, record the source URL, export time, browser version, paper settings and a content identifier so a reviewer can reproduce the result.
  • Do not claim that network-idle, font readiness or one successful run guarantees correctness for every application; verify the actual PDF.

Frequently Asked Questions

Can I export a page that requires a one-time passkey?

Yes, complete the passkey challenge in a controlled headed browser during the authentication setup, save the supported state, and verify that the restored context can open the protected page before generating the PDF.

Why does a PDF contain content I cannot see on screen?

Print CSS, hidden elements, page ranges or a different media emulation setting can change output. Inspect the page’s print styles and the PDF options, then compare the rendered result at the intended viewport.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Is a screenshot the same as a PDF export?

No. A screenshot is an image; a browser PDF preserves a paginated print representation with paper, margins and page-break behavior. Choose the format required by your workflow.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.