Claude PDF Skill: Document Generation Guide
Generating PDFs programmatically is one of those tasks that sounds simple and turns out to be tedious. You pick a library, wire up templates, handle fonts and page breaks, and spend half a day on layout before you’ve written a single line of business logic. The Claude Code /pdf skill compresses that process: describe the document you need, provide your data, and get a working implementation with styled output.
This guide covers how to invoke the /pdf skill, what it produces, and how to use it across the most common document generation scenarios: invoices, reports, and technical specifications. It also covers how to integrate generated PDF code into automated workflows.
How to Invoke the PDF Skill
The /pdf skill is a purpose-built agent within Claude Code. You invoke it like any other skill — prefix your prompt with /pdf and describe what you want.
/pdf generate an invoice PDF for a freelance consulting engagement.
Fields: client name, invoice number, line items with quantity
and rate, subtotal, tax at 8.5%, total due, payment terms.
Style: clean sans-serif, company logo placeholder at top right.
Claude does not just answer conversationally — it reads your project context, identifies relevant files (existing templates, utility functions, package.json), and writes the implementation. If you have no prior PDF setup, it scaffolds one. If you already use a library like pdfkit or puppeteer, it integrates with your existing stack.
The skill understands common document structures. You do not need to specify page margins, font hierarchies, or table rendering logic unless you have specific requirements. The defaults it chooses are publication-quality.
Output from the above invocation would include:
- An
invoiceGenerator.jsmodule exporting agenerateInvoice(data)function - A sample data fixture matching the field schema
- A
generateInvoice.test.jsfile that asserts the output PDF contains expected strings - Installation instructions for any new dependencies
Generating Invoices
Invoices are the most frequent PDF generation use case for solo developers and small teams. The /pdf skill handles the layout complexity that makes invoice generation annoying by hand — specifically, dynamic line item tables that grow with the number of items and push the totals block to the correct position.
Invoke it with your data schema:
/pdf create an invoice generator using pdfkit. Input schema:
{ vendor, client, invoiceNumber, issueDate, dueDate,
lineItems: [{description, quantity, unitPrice}],
taxRate, notes }
Output a PDF buffer that can be streamed in an Express response.
The generated implementation handles the table layout:
// invoiceGenerator.js (generated by /pdf skill)
const PDFDocument = require('pdfkit');
function generateInvoice(data) {
return new Promise((resolve) => {
const doc = new PDFDocument({ margin: 50 });
const buffers = [];
doc.on('data', (chunk) => buffers.push(chunk));
doc.on('end', () => resolve(Buffer.concat(buffers)));
// Header block
doc.fontSize(20).text(data.vendor.name, { align: 'left' });
doc.fontSize(10).fillColor('#666').text(data.vendor.address);
doc.moveDown();
// Invoice metadata
doc.fillColor('#000').fontSize(12)
.text(`Invoice #${data.invoiceNumber}`, { align: 'right' })
.text(`Issued: ${data.issueDate}`, { align: 'right' })
.text(`Due: ${data.dueDate}`, { align: 'right' });
doc.moveDown(2);
// Line items table
const tableTop = doc.y;
const colWidths = [280, 60, 80, 80];
const headers = ['Description', 'Qty', 'Unit Price', 'Amount'];
headers.forEach((h, i) => {
const x = 50 + colWidths.slice(0, i).reduce((a, b) => a + b, 0);
doc.fontSize(10).fillColor('#333').text(h, x, tableTop, { width: colWidths[i] });
});
let y = tableTop + 20;
let subtotal = 0;
data.lineItems.forEach((item) => {
const amount = item.quantity * item.unitPrice;
subtotal += amount;
const row = [item.description, item.quantity, `$${item.unitPrice.toFixed(2)}`, `$${amount.toFixed(2)}`];
row.forEach((cell, i) => {
const x = 50 + colWidths.slice(0, i).reduce((a, b) => a + b, 0);
doc.fontSize(9).fillColor('#000').text(String(cell), x, y, { width: colWidths[i] });
});
y += 18;
});
// Totals
const tax = subtotal * data.taxRate;
doc.y = y + 10;
doc.fontSize(10).text(`Subtotal: $${subtotal.toFixed(2)}`, { align: 'right' });
doc.text(`Tax (${(data.taxRate * 100).toFixed(1)}%): $${tax.toFixed(2)}`, { align: 'right' });
doc.fontSize(12).font('Helvetica-Bold')
.text(`Total: $${(subtotal + tax).toFixed(2)}`, { align: 'right' });
doc.end();
});
}
module.exports = { generateInvoice };
Plugging this into an Express route requires three lines. You have a working invoice endpoint in under an hour including tests.
Generating Reports
Reports differ from invoices because they involve narrative text, charts or tables from dynamic data, and often a cover page. The /pdf skill handles all of these when prompted with sufficient context about the data source.
/pdf generate a weekly sales report PDF. Input: array of daily
sales objects with { date, revenue, units, region }.
Include: cover page with report title and date range,
summary table of totals by region, and a section per region
with day-by-day breakdown. Use puppeteer to render from HTML
so the layout is CSS-driven.
Using Puppeteer for report generation (rather than pdfkit’s programmatic API) gives you CSS layout, which is better for complex multi-column designs. The skill generates an HTML template and a render wrapper:
// reportRenderer.js (generated by /pdf skill)
const puppeteer = require('puppeteer');
async function generateSalesReport(salesData, dateRange) {
const browser = await puppeteer.launch({ headless: 'new' });
const page = await browser.newPage();
const html = buildReportHTML(salesData, dateRange);
await page.setContent(html, { waitUntil: 'networkidle0' });
const pdf = await page.pdf({
format: 'A4',
printBackground: true,
margin: { top: '20mm', bottom: '20mm', left: '15mm', right: '15mm' }
});
await browser.close();
return pdf;
}
The buildReportHTML function it generates produces a complete HTML document with embedded CSS. The benefit of this approach is that you can preview the report in a browser by opening the HTML directly, which makes layout iteration fast without a print-and-check cycle.
Generating Technical Specifications
Technical specifications — API docs, architecture decision records, system design documents — benefit from consistent formatting and the ability to include code blocks, tables, and diagrams. The /pdf skill generates these well when given a structured input schema.
/pdf create a technical specification PDF generator.
Input: { title, version, author, sections: [{heading, body, codeBlocks}] }.
Style: formal document with table of contents, page numbers,
monospace font for code blocks, section numbering. Use pdfmake.
The generated pdfmake definition handles the document definition object, which pdfmake uses to render:
// specGenerator.js (generated by /pdf skill)
const pdfMake = require('pdfmake/build/pdfmake');
const pdfFonts = require('pdfmake/build/vfs_fonts');
pdfMake.vfs = pdfFonts.pdfMake.vfs;
function generateSpec(spec) {
const tocEntries = spec.sections.map((s, i) => ({
text: `${i + 1}. ${s.heading}`,
style: 'tocItem',
pageBreak: undefined
}));
const sectionContent = spec.sections.flatMap((section, i) => {
const blocks = [
{ text: `${i + 1}. ${section.heading}`, style: 'h2', pageBreak: i > 0 ? 'before' : undefined },
{ text: section.body, style: 'body', margin: [0, 8, 0, 8] }
];
if (section.codeBlocks) {
section.codeBlocks.forEach((code) => {
blocks.push({
text: code,
style: 'code',
background: '#f4f4f4',
margin: [0, 4, 0, 12]
});
});
}
return blocks;
});
const docDefinition = {
content: [
{ text: spec.title, style: 'h1' },
{ text: `Version ${spec.version} — ${spec.author}`, style: 'meta', margin: [0, 4, 0, 24] },
{ text: 'Table of Contents', style: 'h2' },
...tocEntries,
...sectionContent
],
styles: {
h1: { fontSize: 22, bold: true },
h2: { fontSize: 14, bold: true, margin: [0, 16, 0, 8] },
meta: { fontSize: 10, color: '#666' },
tocItem: { fontSize: 10, margin: [0, 2] },
body: { fontSize: 10, lineHeight: 1.4 },
code: { font: 'Courier', fontSize: 9 }
}
};
return new Promise((resolve) => {
const pdfDoc = pdfMake.createPdf(docDefinition);
pdfDoc.getBuffer((buffer) => resolve(buffer));
});
}
Automating Document Workflows
Once you have generator functions for invoices, reports, and specs, the next step is wiring them into automated workflows — scheduled report emails, webhook-triggered invoice delivery, CI-generated spec exports.
The /pdf skill can extend the generators it produces. A follow-up prompt:
/pdf add a scheduled job to the sales report generator that runs
every Monday at 8am, queries the database for last week's
sales data, generates the report PDF, and emails it to
addresses listed in REPORT_RECIPIENTS env variable using nodemailer
Claude reads the existing reportRenderer.js, writes a reportScheduler.js using node-cron, and integrates nodemailer — no manual wiring required.
The general pattern for automated PDF workflows is: trigger (cron/webhook/event) -> data fetch -> generator function -> delivery (email/S3/response stream). The /pdf skill understands all four stages and can implement any of them given sufficient context about your existing infrastructure.
Related Reading
Built by theluckystrike — More at zovo.one