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

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.

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

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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

  • format selects a standard paper size such as A4.
  • landscape: true rotates the output.
  • margin accepts CSS lengths for top, right, bottom and left edges.
  • pageRanges limits output to selected pages, for example 1-3.
  • path writes a file where the Lambda process can access it; use /tmp/report.pdf for 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.

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

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.

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

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.

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

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

  1. Choose a supported Node.js runtime and architecture and verify its current AWS support status.
  2. Lock a tested chrome-aws-lambda/Puppeteer pair; do not rely on the old compatibility table for current releases.
  3. Build the zip, layer or container in a compatible Amazon Linux environment.
  4. Set memory, timeout and ephemeral storage using representative documents.
  5. Configure network access for every stylesheet, image, font and authenticated endpoint.
  6. Return base64 only when the integration’s payload limit is sufficient; otherwise upload to S3.
  7. Scope S3 permissions to the required bucket and prefix.
  8. Exercise success, timeout, missing asset, authentication and malformed-URL paths.
  9. Close Chromium in a finally block and log useful error context without secrets.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

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.

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

Images 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.

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

Or 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.

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

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.

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.