Save Full-Page Screenshots of Logged-In Pages as PDF with Playwright
Reuse Playwright authentication state to export logged-in pages as paginated PDFs, or save the full scrollable page as an image.
To save a logged-in page as a PDF, open it in a Playwright browser context that has the user’s authenticated state, wait for the page content you need, then call page.pdf(). Playwright’s PDF output is paginated and uses print CSS by default. For a full-scroll screenshot image instead, use page.screenshot({ fullPage: true }); that produces an image, not a PDF. [Authentication] [PDF API] [Screenshots]
1. Choose the output you need
| Goal | Playwright API | Result |
|---|---|---|
| A normal, printable document | page.pdf() |
Paginated PDF using print CSS by default |
| A screenshot of the whole scrollable page | page.screenshot({ fullPage: true }) |
Tall image such as PNG |
| A single-page PDF that looks like a tall screenshot | Capture a full-page image, then convert it with an image-to-PDF tool | One tall raster page; conversion is separate from Playwright’s documented PDF API |
There is no fullPage option for page.pdf() in the documented API. A PDF is laid out as pages; a full-page screenshot is an image. Pick based on whether readers need paper-like pages or one continuous visual record.
2. Set up Playwright and save authenticated state
Install Playwright for Node.js in a project, then install a browser:
npm install playwright
npx playwright install chromium
Authenticate through the application’s normal login flow and save the browser context state after confirming that login succeeded. Playwright’s authentication guide describes saving state with context.storageState({ path }) and loading it when creating a context. State can contain cookies and other credentials; keep it out of source control.
mkdir -p playwright/.auth
printf '\nplaywright/.auth/\n' >> .gitignore
For example, create save-auth.mjs and adapt the login URL, selectors, and post-login condition to your application:
import { chromium } from 'playwright';
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://app.example.com/login', { waitUntil: 'domcontentloaded' });
await page.getByLabel('Email').fill(process.env.APP_EMAIL);
await page.getByLabel('Password').fill(process.env.APP_PASSWORD);
await page.getByRole('button', { name: 'Sign in' }).click();
// Replace this with a condition that proves login completed for your app.
await page.getByRole('heading', { name: 'Dashboard' }).waitFor({ state: 'visible' });
await context.storageState({ path: 'playwright/.auth/user.json' });
await browser.close();
Run it with credentials supplied through your environment or secret manager. Do not place passwords in the script. If the application uses a multi-factor challenge, complete that supported flow before saving state. The state file is sensitive: anyone who can use valid stored cookies or headers may be able to act as that account. Use a least-privileged account and restrict file access. [Playwright authentication guidance]
3. Create a PDF from the logged-in page
Save this as save-pdf.mjs. It loads the saved state, navigates to the target page, waits for an application-specific readiness condition, and writes a PDF:
import { chromium } from 'playwright';
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
storageState: 'playwright/.auth/user.json',
});
const page = await context.newPage();
await page.goto('https://app.example.com/reports/monthly', {
waitUntil: 'domcontentloaded',
timeout: 60_000,
});
// Prefer a selector that proves the data needed in the export is present.
await page.getByRole('heading', { name: 'Monthly report' }).waitFor({
state: 'visible',
timeout: 30_000,
});
await page.pdf({
path: 'report.pdf',
format: 'A4',
printBackground: true,
margin: { top: '12mm', right: '12mm', bottom: '12mm', left: '12mm' },
});
await browser.close();
Run it with node save-pdf.mjs. Change the URL, heading, and paper settings for your app. The readiness selector matters: a successful navigation can still show a login screen, an empty shell, or data that has not finished loading.
4. Control PDF styling and pagination
page.pdf() uses print media by default. If the website’s screen styles are the desired design, emulate screen media before generating the PDF:
await page.emulateMedia({ media: 'screen' });
await page.pdf({ path: 'report.pdf', printBackground: true });
Use print media when the site has deliberate print styles, such as hidden navigation and simplified tables. Use screen media when you need the screen stylesheet. Check the resulting pagination either way: screen layout may produce awkward page breaks.
| Setting | What it controls | Default or note |
|---|---|---|
format |
Paper format, such as A4 or Letter |
Letter by default |
printBackground |
Whether background graphics and colors are printed | false by default |
margin |
Top, right, bottom, and left page margins | Set explicit values when consistent whitespace matters |
landscape |
Landscape orientation | Useful for wide tables |
pageRanges |
Which pages to include | Empty means all pages |
scale |
Scales page content | Use cautiously; small values can make text hard to read |
preferCSSPageSize |
Whether CSS @page size takes priority |
Use when the page’s print CSS defines paper size |
await page.pdf({
path: 'report.pdf',
format: 'A4',
landscape: true,
printBackground: true,
preferCSSPageSize: true,
pageRanges: '1-4',
margin: { top: '10mm', right: '8mm', bottom: '10mm', left: '8mm' },
});
Consult the Playwright PDF API for the full option list supported by your installed version. If the page declares a CSS @page size, preferCSSPageSize controls whether that size takes precedence over the requested format.
5. Save the full scrollable page as an image
For a full-page screenshot, use the screenshot API. This captures the full scrollable page as an image rather than limiting the capture to the viewport:
await page.screenshot({ path: 'report.png', fullPage: true });
It can be useful for visual review or when a continuous image is the requested artifact. If the image must be delivered inside a PDF, convert it with a separate image/PDF library or tool. A very tall image may be large and difficult to print or read when scaled onto standard paper.
6. Full runnable cURL, Python, and Node.js examples
Playwright is a browser automation library, so its authentication state and page rendering workflow are most direct in Node.js. cURL and Python can request an existing exported PDF or automate a separate service, but they do not load Playwright’s browser storage state and render a page by themselves. Here is a cURL request to download a PDF from an application endpoint that already provides one:
curl --fail --location \
--cookie 'session=REPLACE_WITH_SESSION_COOKIE' \
'https://app.example.com/reports/monthly.pdf' \
--output report.pdf
Use this only if the application exposes a PDF URL and its authentication method accepts that cookie. Avoid putting real session values in shell history or logs. For a site that requires browser execution, use Playwright’s Node.js example above or an authorized browser automation service.
Python can download the same kind of already-generated PDF with a session cookie. It does not replace Playwright’s rendering step:
import os
import requests
url = 'https://app.example.com/reports/monthly.pdf'
response = requests.get(
url,
cookies={'session': os.environ['APP_SESSION_COOKIE']},
timeout=60,
)
response.raise_for_status()
with open('report.pdf', 'wb') as pdf:
pdf.write(response.content)
For browser rendering in Python, use the Playwright Python package and the same storage-state concept. The example assumes you already saved playwright/.auth/user.json and adapts the target URL and readiness selector:
from pathlib import Path
from playwright.async_api import async_playwright
async def main():
async with async_playwright() as p:
browser = await p.chromium.launch()
context = await browser.new_context(
storage_state='playwright/.auth/user.json'
)
page = await context.new_page()
await page.goto(
'https://app.example.com/reports/monthly',
wait_until='domcontentloaded',
timeout=60_000,
)
await page.get_by_role('heading', name='Monthly report').wait_for(
state='visible', timeout=30_000
)
await page.pdf(
path='report.pdf',
format='A4',
print_background=True,
margin={'top': '12mm', 'right': '12mm', 'bottom': '12mm', 'left': '12mm'},
)
await browser.close()
import asyncio
asyncio.run(main())
Install the Python package and browser with pip install playwright and playwright install chromium. Authentication-state files remain sensitive in Python workflows too. See the Python authentication guide and Python PDF API.
7. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| PDF shows a login page | State expired, was saved before login completed, or the app uses session storage not included in standard state | Re-authenticate, verify a post-login page condition before saving state, and follow Playwright’s session-storage guidance if the app depends on it. |
| PDF is blank or missing data | Navigation finished before client-side content loaded | Wait for a specific heading, row, or application-ready signal. Avoid treating a redirect alone as proof of readiness. |
| Colors or backgrounds are missing | PDF background printing is disabled | Set printBackground: true; check print-specific CSS and whether screen media is needed. |
| PDF layout differs from browser | PDF uses print CSS by default, and paper pagination changes layout | Try page.emulateMedia({ media: 'screen' }), set paper and margins deliberately, and inspect page breaks. |
| Content is clipped or too small | Paper size, orientation, margins, or scale do not fit the page | Try landscape for wide content, adjust margins or scale, or use a full-page image if a paginated document is not required. |
| Stored state does not keep the user signed in | The app relies on session storage or another authentication mechanism not covered by the saved state | Check the authentication guide for the application’s storage model and recreate the state through its supported flow. |
| Browser launch fails | The required browser binary is not installed for the Playwright version | Run npx playwright install chromium (or the corresponding install command for your setup) and use a compatible installed package and browser. |
| Navigation times out | Slow application response or an unsuitable navigation wait condition | Set an appropriate timeout and wait for the content needed rather than requiring every network request to finish. |
8. Performance, reliability, and security notes
- Wait for what the document needs. A visible heading or loaded report row is often a better readiness check than assuming a navigation event means all application data is ready.
- Keep captures bounded. Full-page images can become very tall; PDFs with many pages take more time and storage. Capture only required pages with
pageRangeswhen appropriate. - Make output deterministic. Use a stable viewport, paper size, margins, media mode, and application state. Dynamic timestamps, animations, or changing data can alter captures.
- Handle failures explicitly. Set navigation and selector timeouts, close the browser in a
finallyblock in production code, and report whether failure occurred during authentication, navigation, readiness, or export. - Protect credentials. Authentication state can grant account access. Keep it out of Git, artifacts, logs, and shared storage; limit file permissions and account privileges. [Authentication guide]
- Plan resource use. Browser startup and rendering require more resources than a direct file download. Reuse a browser process for controlled batches when appropriate, while creating isolated contexts for separate users or jobs.
9. Or skip the browser setup
If you need a screenshot image of a public page rather than an authenticated PDF workflow, ScreenshotNeo provides a website screenshot API and MCP server. One GET request captures a URL. Cookie banners, popups, and chat widgets are removed before the shot; bot checks, blank pages, and failed loads are never billed. AI agents can capture pages through its MCP server. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000.
See the ScreenshotNeo API documentation. Example request:
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o shot.webp
This endpoint returns a screenshot image; it is not a way to reuse your private Playwright login state or generate the paginated PDF described above. Sign up for 1,000 free screenshots a month with no card.
10. FAQ
Can Playwright make one PDF page as tall as the whole website?
page.pdf() creates a paginated document. To make one tall raster-style page, capture a full-page image and convert it with a separate image-to-PDF step.
Does saving storage state preserve every kind of browser storage?
It covers cookies and supported local storage, IndexedDB, and passkeys depending on the authentication model. Session storage is not persisted by the standard storage-state mechanism; use the guide’s separate approach if required.
Will a PDF always look like the screen?
No. PDF generation uses print CSS by default. Emulate screen media when needed, then account for paper size and pagination.
Can I use this for pages I am not authorized to access?
No. Use an account and session you are authorized to use, and follow the application’s access rules.


