Convert a Webpage to PDF with Landscape Pages Using Puppeteer
Generate a landscape PDF from a webpage with Puppeteer. Set the page size, media type, margins, and readiness checks for predictable output.
Set landscape: true in the options passed to Puppeteer’s page.pdf(). The example below opens a webpage, waits for navigation, and saves a landscape PDF:
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
await page.pdf({ path: 'page.pdf', landscape: true });
landscape defaults to false, so omitting it produces portrait orientation. Orientation is only one part of the result: paper size, print styles, margins, backgrounds, and page readiness can all affect the PDF. See Puppeteer’s PDFOptions and PDF generation guide.
1. Create a landscape PDF with Puppeteer
Install Puppeteer in a Node.js project, then run this complete example. It launches a browser, opens a page, waits for a navigation readiness condition, writes the PDF, and closes the browser even if an operation fails.
const puppeteer = require('puppeteer');
async function main() {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com', {
waitUntil: 'networkidle2',
timeout: 30000,
});
await page.pdf({
path: 'page.pdf',
landscape: true,
});
} finally {
await browser.close();
}
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
The networkidle2 option is an example, not a guarantee that every application has finished rendering. Choose a wait condition that matches the site. If the page fills in content after navigation, wait for a meaningful selector or application-specific signal before calling page.pdf().
2. Choose paper size and page geometry
Set the orientation with landscape, then choose the paper geometry deliberately. Puppeteer supports a named format or explicit width and height. If you provide format together with width or height, format takes priority. Margins are unset by default.
| Option | What it controls | When to set it |
|---|---|---|
landscape |
Landscape or portrait orientation; default is false. |
Set true for a wide page. |
format |
Named paper size. | Use when a standard paper size is appropriate. |
width, height |
Explicit paper dimensions. | Use when you need a custom page size. Do not expect these to win over format if both are supplied. |
preferCSSPageSize |
Whether CSS @page dimensions take priority over API paper dimensions; default is false. |
Set true when the page’s print stylesheet defines the intended sheet size. |
margin |
Space around printed content. | Set explicit margins when content is too close to an edge or needs more room. |
scale |
Content scale; documented range is 0.1 to 2, default 1. | Adjust only when fit or readability requires it. |
Example with a named format, explicit margins, and printed backgrounds:
await page.pdf({
path: 'page.pdf',
format: 'A4',
landscape: true,
margin: {
top: '12mm',
right: '12mm',
bottom: '12mm',
left: '12mm',
},
printBackground: true,
});
Use either a standard format or custom dimensions based on the document you need. There is no one paper size or scale that fits every webpage. For the complete option set and accepted values, refer to the official PDFOptions reference.
3. Control print styles, colors, and backgrounds
page.pdf() renders using the print CSS media type. A site may hide navigation, change widths, or apply other print-specific rules in that mode. If you need the screen CSS instead, emulate screen media before creating the PDF:
await page.emulateMediaType('screen');
await page.pdf({ path: 'page.pdf', landscape: true });
PDF printing modifies colors for print by default. To preserve exact colors, the page can use the CSS property -webkit-print-color-adjust. Background graphics are not included by default; pass printBackground: true when the output needs them.
await page.pdf({
path: 'page.pdf',
landscape: true,
printBackground: true,
});
If you control the webpage’s stylesheet, an exact-color print rule can be applied there:
@media print {
body {
-webkit-print-color-adjust: exact;
}
}
When the printed layout remains unexpectedly narrow or clips content, inspect both the site’s print CSS and any @page rules. Landscape orientation does not force a site to use its screen layout.
4. Wait for the content you need
Navigation completing does not always mean a dynamic application has finished inserting the content you want in the PDF. Puppeteer’s PDF guide says fonts are awaited by default, but late-loading images, client-rendered data, or other application work may need a separate readiness check. Choose a condition tied to the page rather than assuming navigation alone is enough.
await page.goto('https://example.com/report', {
waitUntil: 'domcontentloaded',
timeout: 30000,
});
// Replace this selector with an element that appears when your page is ready.
await page.waitForSelector('[data-report-ready="true"]', {
timeout: 15000,
});
await page.pdf({ path: 'report.pdf', landscape: true });
Use a selector that actually represents finished content in your application. If the target page has no such marker, use a site-specific readiness signal or a deliberate delay, and keep a timeout so a missing signal does not wait forever.
5. Return the PDF from a Node.js service
page.pdf() returns a Uint8Array. You can write it to a path with the path option, as above, or return the bytes from an HTTP handler. This minimal Express route sends the generated PDF directly to the caller:
const express = require('express');
const puppeteer = require('puppeteer');
const app = express();
app.get('/pdf', async (req, res, next) => {
let browser;
try {
const url = req.query.url;
if (typeof url !== 'string' || !url.startsWith('https://')) {
return res.status(400).send('Provide an https URL.');
}
browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
const pdf = await page.pdf({ landscape: true, format: 'A4' });
res.type('application/pdf');
res.set('Content-Disposition', 'inline; filename="page.pdf"');
res.send(Buffer.from(pdf));
} catch (error) {
next(error);
} finally {
if (browser) await browser.close();
}
});
app.listen(3000);
For a public service, validate which URLs the route is allowed to fetch and apply request and execution limits appropriate to your application. Browser navigation can fail or hang, so handle errors and close the browser in all cases.
6. Common problems and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| The PDF is portrait. | landscape was omitted or set to false. |
Pass landscape: true directly to page.pdf(). |
| The layout is narrow, missing elements, or unlike the browser view. | PDF generation uses print media by default, and the site may have print CSS. | Inspect print rules. If screen styling is required, call page.emulateMediaType('screen') before printing. |
| Content is clipped or scaled awkwardly. | Paper size, margins, CSS @page, and scaling may conflict. |
Choose a format or explicit dimensions, check whether CSS page sizing should take priority, and adjust margins or scale deliberately. |
| Background colors or images are absent. | printBackground defaults to false. |
Set printBackground: true. For colors altered for printing, use -webkit-print-color-adjust in the page’s CSS. |
| Some data or images are missing. | The page added content after the navigation condition resolved. | Wait for a meaningful selector or application readiness signal before calling page.pdf(); add a bounded timeout. |
| Navigation times out. | The site did not satisfy the selected readiness condition within the timeout. | Check that the URL is reachable and choose a readiness condition appropriate to the page. Handle timeouts instead of retrying indefinitely. |
Navigation returns null. |
Puppeteer documents null for navigation to about:blank or to the same URL with only a different hash. |
Do not treat a null main-resource response alone as proof that PDF generation failed; inspect the page state and handle the navigation case explicitly. |
| The page itself is a PDF and navigation fails in headless shell. | Puppeteer documents that headless shell does not support navigation to a PDF document. | This is distinct from generating a PDF using page.pdf(). Use a webpage as the source for this workflow. |
7. Performance, reliability, and cost considerations
- Readiness affects latency: waiting for network idle can take longer on pages with continuous requests. A specific content-ready selector can make the wait match the actual task, but set a timeout for it.
- Browser lifecycle affects resource use: close the browser in a
finallyblock so failures do not leave browser processes running. For a service that handles repeated work, manage browser reuse and concurrency according to the service’s resource limits. - Retries need a policy: navigation and page rendering can fail. Retry only errors that may recover, cap attempts, and avoid unbounded retries for invalid URLs or pages that consistently fail.
- PDF size and readability trade off: backgrounds and high-detail page content can increase output size. Scale can help fit content, but excessive reduction makes text harder to read.
- Cost depends on your runtime: Puppeteer itself does not specify a per-PDF price in the cited API documentation. Account for the compute and browser infrastructure you operate; the dossier provides no benchmark or universal cost per capture.
8. Or skip the browser setup
If you only need a rendered capture, ScreenshotNeo takes a URL in one API request and can return PNG, JPEG, WebP, or PDF. Its API supports PDF options such as paper size, margins, landscape, and page ranges; see the ScreenshotNeo documentation for the PDF request configuration.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
The same request in 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)
And in 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}`);
These calls show the standard one-request screenshot example; consult the docs for the PDF and landscape parameters before using it for PDF output. ScreenshotNeo removes cookie banners, popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed, and responses identify the page verdict and billing status. An MCP server lets AI agents use screenshot tools. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Learn about ScreenshotNeo and sign up for 1,000 free screenshots a month with no card.
9. FAQ
Does landscape: true change a webpage’s responsive layout?
It sets the PDF page orientation. The page’s CSS and viewport behavior still determine how its content is laid out.
Does Puppeteer wait for fonts before making the PDF?
The PDF guide says font loading is awaited by default. Other late-loading content may still need an explicit readiness strategy.
Can I use CSS @page dimensions?
Yes. Set preferCSSPageSize: true when CSS page dimensions should take priority over the API paper dimensions.
Does generating a PDF with page.pdf() require navigating to a PDF?
No. The workflow navigates to a webpage and generates a PDF from it. Puppeteer’s headless-shell warning concerns navigating to a PDF document as the page itself.


