To generate a PDF in AWS Lambda, launch the Chromium binary exposed by chrome-aws-lambda, navigate a Puppeteer page to your HTML or URL, call page.pdf(), and return or store the resulting bytes. The reliable pattern is to pair compatible package versions, allocate enough memory and temporary storage, wait for assets to load, and close the browser in a finally block.
What the PDF workflow actually does
chrome-aws-lambda supplies a Lambda-compatible Chromium executable and launch defaults. Puppeteer supplies the page API; PDF generation itself is performed by page.pdf(). The method renders using print CSS by default and returns PDF data as a byte array. A Lambda handler can return those bytes through an API Gateway response or upload them to durable storage such as Amazon S3.
The package README demonstrates launching Chromium and opening a page, but its example returns a title rather than a PDF. Add the Puppeteer PDF call yourself and validate the exact API exposed by the versions you deploy.
Compatibility comes before code
Pair chrome-aws-lambda and Puppeteer deliberately
The package instructs you to install chrome-aws-lambda with its corresponding puppeteer-core (or Puppeteer) version. Its published compatibility table ends at Puppeteer 10.1, chrome-aws-lambda 10.1 and Chromium revision 92. That is historical information, not proof that the combination works with a current Lambda runtime.
#1 Best Overall
Before deployment, choose the Lambda Node.js runtime and CPU architecture, then verify that the Chromium binary, Puppeteer API and native dependencies support that exact combination. Build and test the same artifact you will deploy. AWS runtime identifiers and deprecation schedules change; a deprecated runtime can lose patches and technical support, so consult the current AWS Lambda runtime table when you select a runtime.
Install the dependencies
A typical project installs the browser package and a matching Puppeteer Core release:
npm install chrome-aws-lambda puppeteer-core
Do not blindly install the newest Puppeteer Core beside an old chrome-aws-lambda release. A protocol mismatch can appear as launch failures, missing browser methods or navigation errors. Lock versions in package-lock.json, and repeat compatibility testing after upgrades.
A complete Lambda handler
The following handler follows the package launch contract, generates an A4 PDF, and returns a base64-encoded API response. It is an implementation starting point: test it with your selected versions, trigger type and response limits.
const chromium = require('chrome-aws-lambda');
exports.handler = async (event) => {
let browser;
try {
const target = event.url || 'https://example.com';
browser = await chromium.puppeteer.launch({
args: chromium.args,
defaultViewport: chromium.defaultViewport,
executablePath: await chromium.executablePath,
headless: chromium.headless,
});
const page = await browser.newPage();
await page.goto(target, {
waitUntil: 'networkidle2',
timeout: 60000,
});
const pdf = await page.pdf({
format: 'A4',
printBackground: true,
preferCSSPageSize: true,
margin: {
top: '16mm',
right: '14mm',
bottom: '16mm',
left: '14mm',
},
});
return {
statusCode: 200,
headers: { 'Content-Type': 'application/pdf' },
body: Buffer.from(pdf).toString('base64'),
isBase64Encoded: true,
};
} finally {
if (browser) {
await browser.close();
}
}
};
For an HTTP trigger, validate the URL instead of accepting arbitrary destinations. Unrestricted URL input can turn your function into a server-side request proxy. Apply an allowlist, authentication and request-size limits appropriate to your application.
Rendering your own HTML instead of a URL
Use page.setContent() when the document is assembled in the function. External stylesheets, fonts, images and scripts still need time to finish. A practical sequence is to set the content, wait for network activity to settle, and optionally wait for a selector that proves the application finished rendering.
await page.setContent(html, { waitUntil: 'networkidle0', timeout: 60000 });
await page.waitForSelector('#report-ready', { timeout: 15000 });
const pdf = await page.pdf({
format: 'A4',
printBackground: true,
preferCSSPageSize: true,
});
If your HTML does not contain a readiness marker, use a measured delay only as a fallback. A fixed delay can waste invocation time or still be too short on a slow network.
PDF options that affect the result
Paper, orientation and margins
formatselects a standard paper size such asA4.landscape: truerotates the output.marginaccepts CSS lengths for top, right, bottom and left edges.pageRangeslimits output to selected pages, for example1-3.pathwrites a file where the Lambda process can access it; use/tmp/report.pdffor transient storage.
CSS sizing and backgrounds
preferCSSPageSize: true allows an @page rule in your stylesheet to override the API paper size. Set printBackground: true when colored sections, background images or shaded table cells matter. For exact color reproduction, add -webkit-print-color-adjust: exact to the relevant CSS.
Print media versus screen media
page.pdf() uses print media rules. If the page is designed for the screen and you want those styles, select screen media before printing:
await page.emulateMediaType('screen');
const pdf = await page.pdf({
format: 'A4',
printBackground: true,
});
Media behavior and defaults can vary between Puppeteer releases. Verify the documentation and tests for the version in your lockfile.
Fonts and layout stability
Puppeteer waits for fonts by default, but a font can still fail because its URL requires authentication, is blocked by a policy, or is unavailable from the Lambda network. Embed critical fonts, make them publicly reachable when appropriate, or wait for a page-specific readiness condition. Check the generated PDF for fallback fonts, clipped content, and unexpected page breaks.
Returning bytes or storing a durable PDF
Return bytes for small responses
The handler above keeps the PDF in memory and returns it base64 encoded. This is suitable only when your invocation integration accepts the resulting response size. Base64 increases the payload size, so check the limits of API Gateway, an Application Load Balancer, or your direct invocation path before choosing this design.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Rank #3
Use /tmp for transient files
Lambda’s ephemeral storage is configurable from 512 MB to 10,240 MB. The directory is temporary and belongs to a particular execution environment; it is not a permanent document store. Use it for Chromium extraction or an intermediate PDF, then remove files when they are no longer needed.
Upload to S3 for durable access
For larger documents, asynchronous jobs or downloads that outlive the invocation, keep the returned PDF bytes or write a file under /tmp, upload it to an S3 bucket, and return an object key or an authorized download URL. Give the execution role only the bucket and actions required, such as s3:PutObject on a specific prefix. Do not copy a broad learning-example policy into production.
const fs = require('node:fs/promises');
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
const s3 = new S3Client({});
// after page.pdf({ path: '/tmp/report.pdf' })
await s3.send(new PutObjectCommand({
Bucket: process.env.PDF_BUCKET,
Key: `reports/${event.id}.pdf`,
Body: await fs.readFile('/tmp/report.pdf'),
ContentType: 'application/pdf',
}));
Configure an S3 lifecycle rule if generated files should expire. A signed URL can provide time-limited access without making the bucket public.
Lambda packaging and resource planning
Bundle the native browser correctly
Deploy a zip artifact, Lambda layer or container image containing the Chromium binary and Node.js dependencies built for the Lambda Amazon Linux environment and your selected architecture. The project’s documented layer workflow is one option. A package built on an incompatible local operating system can fail before your handler runs.
Memory, timeout and concurrency
The project README historically recommends at least 512 MB and suggests 1,600 MB or more. Treat those values as package-specific guidance, not a universal AWS requirement. Memory affects CPU allocation, so increasing it can reduce rendering time, but the correct setting depends on HTML complexity, image size, font loading, page count and concurrent invocations. Benchmark representative documents and set the timeout above the slowest expected navigation and print operation.
Each concurrent invocation can launch a browser and consume memory, CPU and temporary disk. Consider reserved or account concurrency limits, reuse a warm browser only with careful isolation, and close every page and browser on success and failure. A single invocation that renders multiple pages should also close each page when it is finished.
Deployment checklist
- Choose a supported Node.js runtime and architecture and verify its current AWS support status.
- Lock a tested
chrome-aws-lambda/Puppeteer pair; do not rely on the old compatibility table for current releases. - Build the zip, layer or container in a compatible Amazon Linux environment.
- Set memory, timeout and ephemeral storage using representative documents.
- Configure network access for every stylesheet, image, font and authenticated endpoint.
- Return base64 only when the integration’s payload limit is sufficient; otherwise upload to S3.
- Scope S3 permissions to the required bucket and prefix.
- Exercise success, timeout, missing asset, authentication and malformed-URL paths.
- Close Chromium in a
finallyblock and log useful error context without secrets.
Troubleshooting common failures
Chromium will not launch
Check the package pair, architecture, native libraries and executable path. Confirm that the deployment artifact contains the package’s extracted binary and that the Lambda runtime matches the build environment. Increasing memory will not repair an incompatible binary.
Navigation times out
Inspect DNS, VPC egress, security groups and the target site’s response time. Replace networkidle2 with a deliberate readiness selector for pages that keep long-lived connections open. Increase the timeout only after confirming the page can load within the function’s overall timeout.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Repair Windows errors before they cause bigger problems3Fix the driver behind crashes, sound loss and screen glitchesImages or fonts are missing
Verify the asset URLs from inside Lambda, authentication headers and certificate validity. Wait for the relevant selector or font-loading state, and ensure the PDF is not generated before client-side rendering completes.
Colors or layout differ from the browser
Remember that print CSS is the default. Try emulateMediaType('screen'), enable printBackground, add print-specific CSS and decide whether preferCSSPageSize should honor your @page rule.
The response is too large
Do not force a large base64 response through an integration with a smaller payload limit. Write to /tmp or use the in-memory bytes, upload to S3, and return a key or signed URL.
Invocations become slow or run out of memory
Reduce image dimensions, avoid loading unnecessary resources, increase memory and temporary storage where measurements justify it, and limit concurrency. Capture timing for navigation, asset loading and PDF creation separately so the bottleneck is visible.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallOutdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchOr skip the browser setup
If you need a screenshot or PDF endpoint rather than an in-function Chromium deployment, ScreenshotNeo provides a single API request. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups and chat widgets; each step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients.
cURL
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 documentation for PDF parameters and the complete option set. Features include full-page capture with lazy images loaded, CSS-selector element capture, device presets and custom viewports, retina scale, paper size, margins, landscape mode, page ranges, custom CSS and JavaScript, click and wait actions, request blocking, headers, cookies, user agents, authorization, timezone and geolocation, transparent backgrounds, resizing, configurable caching, signed image links, asynchronous webhooks, bulk capture of up to 100 URLs per call, usage data and an OpenAPI specification. Existing parameter names used by other screenshot APIs also work.
Every plan includes all features. The Free plan provides 1,000 shots per month without a card; paid plans start at $5 for 3,000 shots, with yearly billing providing two months free. Create a free ScreenshotNeo account to try it.
Frequently Asked Questions
Can I use chrome-aws-lambda with any current Lambda Node.js runtime?
No. The visible package matrix is historical and ends at Puppeteer 10.1/Chromium 92. Verify and test the exact runtime, architecture, Chromium build and Puppeteer version you intend to deploy.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Should I generate the PDF in memory or write a file?
Use memory for a response that fits your integration’s payload limit. Use /tmp plus S3 when the file must persist, is large, or will be downloaded asynchronously.
Why does my PDF ignore the page’s screen design?
Puppeteer’s PDF method uses print media. Call page.emulateMediaType(‘screen’) before page.pdf() when screen styles are the intended design, and enable printBackground for backgrounds.
Quick Recap
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.

