Use the actual Google Font file, not a Google Fonts CSS URL. Download the family and style you need, then give PDFKit a supported path, Buffer, or parsed font object. In Node.js, call doc.font() or register an alias with doc.registerFont(). In a browser build, fetch the bytes, convert them to Uint8Array, register them with registerFile(), and then register that path with PDFKit.
What PDFKit needs from Google Fonts
Google Fonts offers two related services that are easy to confuse:
- The CSS API returns a stylesheet for a web page. A browser reads that stylesheet and downloads an appropriate web-font resource.
- The font catalog/Developer API exposes family metadata such as variants, subsets and file URLs, which can help an application locate a font file.
PDFKit does not embed a family name or a stylesheet link by itself. Its font API consumes font data: a filesystem path, a Buffer, or a parsed fontkit Font object. Therefore, adding a <link> for Google Fonts to HTML does not make that typeface available to a separately generated PDFKit document. Obtain the file for the exact family, weight, style and script coverage, and load that file through PDFKit.
Choose the family, style and glyph coverage first
Match every style you will use
If the document contains regular, bold or italic text, obtain those faces explicitly. Do not assume that a regular face can produce a correct bold or italic design. A variable font can be appropriate when its available axes cover your required weights or styles, but verify the file and the PDFKit version in your target runtime.
#1 Best Overall
Check scripts and subsets
Select a file that contains the scripts and glyphs in your content. A Latin-only subset will not render Cyrillic, Greek, Arabic, CJK or other required characters. Metadata from Google’s font catalog can show available variants and subsets; inspect the selected family rather than relying on a generic download link.
Pick a PDFKit-supported format
PDFKit documents support for TrueType (.ttf), OpenType (.otf), WOFF, WOFF2, TrueType Collection (.ttc) and Datafork TrueType (.dfont). A TTF is the simplest format for a first implementation. Collections can contain several faces; when using a collection, provide the style name that should be extracted.
Node.js: embed a downloaded Google Font
Download the chosen file into your project, for example fonts/Roboto-Regular.ttf. Keep the file with your application or make its path configurable. Install PDFKit in the project, then create the PDF as a writable stream:
const PDFDocument = require('pdfkit');
const fs = require('node:fs');
const doc = new PDFDocument({ margin: margins });
const output = fs.createWriteStream('google-font-example.pdf');
doc.pipe(output);
doc.registerFont('Body', './fonts/Roboto-Regular.ttf');
doc.font('Body').fontSize(16)
.text('Text rendered with a Google Font file.');
doc.end();
Replace the accidental placeholder in the constructor with a number if you use margins; the minimal, runnable version is:
Free tools Windows power users keep installed
One-click scans. No signup required.
const PDFDocument = require('pdfkit');
const fs = require('node:fs');
const doc = new PDFDocument();
doc.pipe(fs.createWriteStream('google-font-example.pdf'));
doc.registerFont('Body', './fonts/Roboto-Regular.ttf');
doc.font('Body').fontSize(16)
.text('Text rendered with a Google Font file.');
doc.end();
registerFont(name, path, familyStyle) creates a reusable alias. The alias is useful when a document switches among several faces. PDFKit also accepts a Buffer:
const fontData = fs.readFileSync('./fonts/Roboto-Regular.ttf');
doc.registerFont('BodyBuffer', fontData);
doc.font('BodyBuffer').text('The font came from a Buffer.');
For a one-off use, skip the alias:
doc.font('./fonts/Roboto-Regular.ttf')
.fontSize(16)
.text('Hello with a directly selected font.');
Call font() before writing the text that should use that face. Registering a font does not retroactively change text already written.
Browser PDFKit: fetch and register the bytes
Browser builds cannot read a server filesystem path. Fetch the font, convert the response to a Uint8Array, and register that byte array under a path. Use the identical path in registerFont():
import PDFDocument, { registerFile } from 'pdfkit';
const response = await fetch('/fonts/Roboto-Regular.ttf');
if (!response.ok) {
throw new Error(`Font request failed: ${response.status}`);
}
const fontData = new Uint8Array(await response.arrayBuffer());
const fontPath = 'fonts/Roboto-Regular.ttf';
registerFile(fontPath, fontData);
const doc = new PDFDocument();
doc.registerFont('Roboto', fontPath);
doc.font('Roboto')
.fontSize(16)
.text('Text rendered with a Google Font file.');
doc.end();
The file registry belongs to the loaded PDFKit module. Registering the same path again replaces its data; passing undefined unregisters it. This matters in single-page applications that reload or replace font assets.
Recommended Free Tools
Collect the browser PDF output
PDFKit emits a readable stream. Your browser integration must collect chunks and create a Blob, or use the PDFKit browser helpers available in your version. The helpers named toBlob and toBytes are documented as experimental, so treat them as version-sensitive.
const chunks = [];
doc.on('data', chunk => chunks.push(chunk));
doc.on('end', () => {
const blob = new Blob(chunks, { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'google-font-example.pdf';
link.click();
URL.revokeObjectURL(url);
});
doc.end();
Using several weights, italics or a collection
Register each face under a distinct alias and select it immediately before the relevant text:
doc.registerFont('Regular', './fonts/Roboto-Regular.ttf');
doc.registerFont('Bold', './fonts/Roboto-Bold.ttf');
doc.registerFont('Italic', './fonts/Roboto-Italic.ttf');
doc.font('Regular').text('Normal paragraph');
doc.font('Bold').text('Bold paragraph');
doc.font('Italic').text('Italic paragraph');
For a .ttc or .dfont containing multiple styles, pass the collection’s style name through the registration API as documented by your PDFKit version. Do not guess a style identifier: inspect the collection metadata or use separate files when that is simpler.
PDF/A and embedding requirements
For PDF/A output, fonts must be embedded. PDFKit’s standard PDF fonts are AFM metrics without embeddable font data, so they are not suitable for this requirement. Use registerFont() with an embeddable file such as TTF, and validate the resulting document with the PDF/A checker used by your workflow.
Licensing and redistribution
Google states that its font collection is released under open-source licenses and may be used in commercial and non-commercial projects. That overview does not replace the license for the specific family. If you bundle a font in an application, container image or downloadable PDF workflow:
- Keep the family’s license and notice files with your project when required.
- Confirm that the exact selected style and any variable-font file are covered.
- Check whether your distribution method has additional notice or attribution obligations.
Production checklist
- Choose the family, weights, styles, variable axes and scripts your text needs.
- Obtain a current font file in a format supported by your installed PDFKit release.
- Store the file at a stable path or fetch it from a controlled, versioned asset URL.
- Load it as a path or Buffer in Node.js; in a browser, fetch bytes and call
registerFile(). - Register aliases for every face you will select, then set
doc.font()before each text run. - Generate the PDF and inspect it in more than one viewer, including characters from every required script.
- For archival output, verify embedding and PDF/A conformance.
- Retain the exact family license alongside the implementation.
Troubleshooting common failures
“Cannot find module” or “font file not found”
The path is resolved by the Node process’s working directory, not necessarily by the source file’s directory. Use an absolute path built from the module location, confirm the file exists in the deployed image, and check filename case on Linux.
The browser says the font path cannot be opened
A browser cannot read ./fonts/... as a server filesystem path. Fetch the file, convert it to Uint8Array, call registerFile(path, bytes), and pass that same registered path to registerFont().
The PDF uses a fallback face or missing glyph boxes
Verify that the selected file really contains the missing glyphs and that you selected the intended weight/style. A subset or Latin-only file cannot render characters outside its coverage. Test the actual text, not only an English sample.
Bold or italic text looks wrong
Register the real bold or italic file instead of expecting PDFKit to synthesize a face from regular. For variable fonts, confirm that the chosen axes and PDFKit/fontkit combination handle the file as expected.
The file downloads but the PDF is incomplete
In Node, pipe the document to a writable stream and call doc.end(). In a browser, wait for the stream’s end event before constructing the Blob. Handle stream errors and do not revoke a Blob URL until the download or viewer has received it.
A collection font selects the wrong face
Use the collection’s explicit style selector when registering it, or extract and ship separate files. A family name alone is not a reliable selector for every collection.
The PDF/A validator rejects the document
Replace PDFKit standard fonts with an embedded TTF or another supported embeddable file, then regenerate and validate again. Also check that every font used by headers, footers and fallback runs is embedded.
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 matchPerformance, reliability and caching
Font loading is normally a one-time cost per process or browser session. In Node, keep a resolved path or cached Buffer and reuse registered aliases across documents. In the browser, cache the fetched response with your normal asset strategy, but invalidate it when you intentionally change the font version. Do not fetch a font for every text run.
Rank #4
For reliable builds, pin the asset you selected rather than depending on an undocumented, changing URL. Log the family, style and file version used to create a document. A successful HTTP response does not prove that the file contains every glyph your users will submit, so include representative multilingual test strings in CI.
Or skip the browser setup
If your actual goal is to obtain a clean image or PDF of a web page rather than generate a PDF with a custom embedded font, ScreenshotNeo provides a single HTTP endpoint. Its capture pipeline accepts cookie and consent banners, removes more than 60 known consent platforms plus newsletter popups and chat widgets, and lets you turn each cleanup step off. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and the response reports the page verdict and billing result in X-Page-Verdict and X-Billed headers.
For PDFKit font embedding, continue using the file workflow above. For website capture, this is the direct call:
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 →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 all request options. The same service also supports PDF output, full-page and element captures, device presets and custom viewports, retina scale, dark mode, custom CSS and JavaScript, click and wait actions, request blocking, cookies and headers, timezone and geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous webhooks, bulk capture of up to 100 URLs per call, usage reporting and an OpenAPI specification. An 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 fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan, and yearly billing gives two months free. Sign up for the free ScreenshotNeo account.
Frequently Asked Questions
Can I pass a Google Fonts CSS URL directly to PDFKit?
No. PDFKit needs the font’s bytes through a path, Buffer or parsed font object; a CSS stylesheet link is a browser web-font mechanism.
Which Google Font format should I start with?
TTF is the most straightforward documented choice, although PDFKit also lists OTF, WOFF, WOFF2, TTC and DFONT support.
Do I need separate files for regular and bold?
Use separate files or verified variable-font axes for each style you need; do not rely on synthetic styling.
How do I use a font in a browser bundle?
Fetch its bytes, convert the response to Uint8Array, register them with registerFile(), and use that exact registered path with registerFont().
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.




