pdfkit generate PDF: working versions (tested 2026-10-16)
Build a PDFDocument with pdfkit, stream it into a buffer, and assert the output starts with %PDF using only built-in fonts offline.
passed2 passed · 0 failed2026-10-16 02:18 UTC- Status
- passed
- Last passed
- 2026-10-16 02:18 UTC
- Tests
- 2 / 2
- Language
- TypeScript
- Runtime
- node v22.23.3
pinned versions3
pdfkit@0.20.2@types/node@26.6.5tsx@4.23.15
run it
npm install --no-audit --no-fund --ignore-scripts=false npm test
test environment
node v22.23.3 linux 6.17.0-1022-azure x64 runner: github-actions 38839686104
The code
Exactly the files that passed. Your agent can pull them with get_recipe("pdfkit-generate-pdf") (install), or see them on GitHub.
package.json
{
"name": "pdfkit-generate-pdf",
"private": true,
"type": "module",
"scripts": {
"test": "node --import tsx --test test/*.test.ts"
},
"dependencies": {
"pdfkit": "0.20.2"
},
"devDependencies": {
"@types/node": "26.6.5",
"tsx": "4.23.15"
}
}
src/pdf.ts
import PDFDocument from "pdfkit";
import { PassThrough } from "node:stream";
export type Row = [string, string, string];
/**
* Renders a small document (heading, paragraph, table-like rows) into a buffer.
* Uses only pdfkit's built-in standard fonts, so no network access is required.
*/
export function generatePdf(title: string, body: string, rows: Row[]): Promise<Buffer> {
return new Promise((resolve, reject) => {
const doc = new PDFDocument({ size: "A4", margin: 50 });
const stream = new PassThrough();
const chunks: Buffer[] = [];
stream.on("data", (chunk: Buffer) => chunks.push(chunk));
stream.on("end", () => resolve(Buffer.concat(chunks)));
stream.on("error", reject);
doc.pipe(stream);
doc.font("Helvetica-Bold").fontSize(20).text(title);
doc.moveDown();
doc.font("Helvetica").fontSize(11).text(body, { align: "justify" });
doc.moveDown();
const columns = [50, 260, 410];
let y = doc.y;
rows.forEach((row, index) => {
doc.font(index === 0 ? "Helvetica-Bold" : "Helvetica").fontSize(11);
doc.text(row[0], columns[0], y);
doc.text(row[1], columns[1], y);
doc.text(row[2], columns[2], y);
y += 20;
doc.moveTo(50, y - 6).lineTo(545, y - 6).stroke();
});
doc.end();
});
}
test/pdf.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { generatePdf } from "../src/pdf.ts";
test("generates a PDF starting with %PDF and a non-trivial length", async () => {
const pdf = await generatePdf(
"Quarterly Report",
"This document is produced entirely offline using pdfkit's built-in fonts.",
[
["Region", "Units", "Revenue"],
["North", "120", "$4,800"],
["South", "95", "$3,700"],
["East", "140", "$5,600"],
],
);
assert.ok(Buffer.isBuffer(pdf));
assert.equal(pdf.subarray(0, 5).toString("latin1"), "%PDF-");
assert.ok(pdf.length > 500, `expected a non-trivial length, got ${pdf.length}`);
});
test("writes a valid EOF marker", async () => {
const pdf = await generatePdf("Empty", "No rows here.", [["A", "B", "C"]]);
assert.ok(pdf.subarray(0, 5).toString("latin1").startsWith("%PDF"));
assert.ok(pdf.subarray(-1024).toString("latin1").includes("%%EOF"));
});
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
~/.cursor/mcp.json
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }.vscode/mcp.json
{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }~/.codeium/windsurf/mcp_config.json
{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }MCP settings
{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.