How to Set PDF Title and Author Metadata in Puppeteer
Puppeteer does not expose PDF title or author options. Generate the PDF, then set metadata with pdf-lib and verify the saved file.

Direct answer: Puppeteer’s documented Page.pdf() options do not include PDF document title or author fields. Generate the PDF with Puppeteer, load the resulting bytes with a PDF library such as pdf-lib, call setTitle() and setAuthor(), then save the updated bytes. The metadata belongs to the PDF document and does not automatically add a visible heading or byline to the page.
This pattern works whether Puppeteer returns a Uint8Array in memory or writes a file with its path option. If exact metadata matters to your workflow, inspect the final saved PDF after the metadata pass because PDF readers do not all display document information in the same way.
What Puppeteer can and cannot set
The Puppeteer PDFOptions reference documents print settings such as paper format, margins, orientation, backgrounds, page ranges, headers and footers, scaling, and output location. It does not document title or author properties for page.pdf(). The Page.pdf() method returns PDF bytes, which gives you a clean handoff point to a PDF editing library.
| Requirement | Where to implement it |
|---|---|
| Visible report title | HTML/CSS rendered before calling page.pdf() |
| PDF document title field | pdfDoc.setTitle('…') after generation |
| PDF author field | pdfDoc.setAuthor('…') after generation |
| Filename | Puppeteer path or your filesystem code |
| Browser tab title | The HTML <title> element; this is separate from PDF metadata |
Complete JavaScript implementation
Install both packages in a Node.js project:

npm install puppeteer pdf-lib
The following script navigates to a page, creates a PDF in memory, sets title and author metadata, and writes the final document:
import puppeteer from 'puppeteer';
import { PDFDocument } from 'pdf-lib';
import { writeFile } from 'node:fs/promises';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
const puppeteerBytes = await page.pdf({
format: 'A4',
printBackground: true,
preferCSSPageSize: true
});
const pdfDoc = await PDFDocument.load(puppeteerBytes);
pdfDoc.setTitle('Example report');
pdfDoc.setAuthor('Example Company');
const updatedBytes = await pdfDoc.save();
await writeFile('report.pdf', updatedBytes);
} finally {
await browser.close();
}
This combines the documented Puppeteer byte output with the documented pdf-lib setters. The pdf-lib API source defines setTitle(title) and setAuthor(author). Its title options also include showInWindowTitleBar, which requests that compatible readers show the document title instead of the filename.
Using an HTML title and a metadata title together
Set the visible title in your page markup and the document property separately:
<!doctype html>
<html>
<head>
<title>Quarterly Report</title>
<style>
@page { size: A4; margin: 18mm; }
h1 { font: 700 28px system-ui; }
</style>
</head>
<body>
<h1>Quarterly Report</h1>
<p>Prepared for Example Company</p>
</body>
</html>
The HTML heading is visible on the page. The PDF metadata is stored in the document information dictionary and is exposed by readers, file managers, search tools, and cataloguing systems that read PDF properties. PDF 32000-1:2008 describes title and author as general document information intended to help cataloguing and searching.
Choosing Puppeteer PDF options before metadata
Metadata editing does not change layout. Configure the print output first:
format,width, andheightcontrol page dimensions.landscapechanges orientation.margincontrols printable margins.printBackground: truepreserves CSS backgrounds.displayHeaderFooter,headerTemplate, andfooterTemplateadd printed running content.pageRangeslimits output to selected pages.preferCSSPageSizelets an HTML@pagerule take precedence.scalechanges rendered size without changing the paper format.pathwrites Puppeteer’s initial PDF directly to disk; you still need a second save after metadata editing.
Wait for the page state your application needs before calling pdf(). networkidle2 is useful for pages that finish loading after several requests, but a page with long polling may never become idle. In that case, wait for a specific selector or use a bounded delay.
Working with files instead of in-memory bytes
If another process already produced a PDF, load it and update the fields:
import { readFile, writeFile } from 'node:fs/promises';
import { PDFDocument } from 'pdf-lib';
const input = await readFile('puppeteer-output.pdf');
const pdfDoc = await PDFDocument.load(input);
pdfDoc.setTitle('Invoice 2026-09');
pdfDoc.setAuthor('Accounts Team');
await writeFile('invoice-final.pdf', await pdfDoc.save());
Use a different output path when you want a rollback copy. If you overwrite the original, write to a temporary file first and rename it only after save() succeeds.
Metadata values, encoding, and reader behavior
- Pass ordinary JavaScript strings to
setTitle()andsetAuthor(). Unicode names are valid, but inspect the final file in your target readers. - Do not confuse the filename with the title field. Renaming
report.pdfdoes not set document metadata. - Do not assume a metadata title becomes visible in the page content. Add an HTML heading when readers must see it in print.
- Some readers display the title in a window or tab, while others show only the filename. pdf-lib describes
showInWindowTitleBaras working for most readers, not every reader. - PDF viewers may cache recently opened properties. Close and reopen the file when checking a changed value.

Verification checklist
- Generate the PDF and run the metadata pass.
- Confirm that the final file exists and has a non-zero size.
- Open Document Properties in at least one target PDF reader.
- For automated pipelines, read the saved file with a metadata inspection tool or load it again with your PDF library.
- Check both fields after any later operation that rewrites the PDF, because a different library may replace document information.
Common errors and fixes
| Error or symptom | Cause | Fix |
|---|---|---|
page.pdf({ title: ... }) has no effect |
title is not a documented Puppeteer PDF option. |
Generate bytes, then call setTitle() with pdf-lib. |
| Author appears blank | The file being opened is the pre-edit Puppeteer output. | Open the file written after pdfDoc.save(). |
| Visible title is missing | Metadata does not create page content. | Add an HTML heading or template before calling page.pdf(). |
Failed to launch the browser process |
Chromium dependencies, executable path, or sandbox settings are wrong. | Install Puppeteer’s browser, provide a valid executable path when required by your deployment, and follow the container’s Chromium dependency guidance. |
Timed out after navigation |
The site keeps requests open or is slow. | Use a longer bounded timeout, wait for a stable selector, or choose a less strict navigation condition. |
Invalid PDF from pdf-lib |
Input bytes are truncated or are not a PDF. | Check the Puppeteer result, file transfer, and content type before loading. |
| Fonts or images are absent | Resources were not loaded before printing. | Wait for required selectors and fonts, use printBackground, and make sure remote assets are reachable from the browser. |
| Metadata disappears after another edit | A later PDF writer replaced the document information. | Apply metadata last, or copy the fields again after the final rewrite. |
Performance, reliability, and cost considerations
Launching Chromium is usually more expensive than setting two metadata fields. Keep a browser process alive for batches, create and close pages per job, and avoid loading unnecessary assets when the source permits it. Reuse a single page only when you can reliably clear cookies, storage, event handlers, and injected styles between jobs.
Bound every navigation and metadata operation. Always close the browser in a finally block so a failed page does not leak a process. For retries, distinguish navigation failures from PDF parsing failures: retrying a malformed byte buffer will not fix it. Save to a temporary path and atomically replace the destination to prevent consumers from reading a partially written PDF.
The Puppeteer workflow consumes your application’s compute, browser runtime, storage, and bandwidth. If you generate many screenshots or PDFs, a hosted capture service can remove browser maintenance and provide request-level status and billing signals.
Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server. It can return PNG, JPEG, WebP, or PDF from one GET request, with options for paper size, margins, landscape mode, and page ranges. Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and each response reports its result through X-Page-Verdict and X-Billed headers.
For a PDF capture, call the API and save the response:
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o shot.webp
See the ScreenshotNeo documentation for PDF parameters and the other capture options. The same service supports custom CSS and JavaScript, selectors, device presets, retina scale, headers, cookies, user agents, authorization, waiting rules, blocking, caching, signed links, asynchronous jobs, webhooks, bulk capture, and a usage API. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.
Python:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
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}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const bytes = await res.arrayBuffer();
await Bun.write('shot.webp', bytes);
The free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 shots, and every feature is available on every plan. Create a free ScreenshotNeo account.
Frequently asked questions
Can I pass title and author directly to page.pdf()?
Not according to the documented PDFOptions reference. Use a post-generation PDF library.
Does setting metadata add a title page?
No. It changes document properties. Render visible text in HTML if you need a title page or byline.
Can I set metadata when Puppeteer writes with path?
Yes. Read that file with pdf-lib, set the fields, and save a final file.
Will every PDF viewer show the title instead of the filename?
No. Reader behavior varies. The pdf-lib window-title option is described as working for most readers, so verify in the applications that matter to you.
What if a later PDF operation removes the fields?
Apply title and author after the last operation that rewrites the PDF, then verify the final artifact.


