The pdfnoodle package is a TypeScript-first Node.js SDK with zero runtime dependencies. It uses native fetch (Node.js 20+) and ships as both ESM and CommonJS.
Installation
Setup
import { PdfNoodle } from 'pdfnoodle';
const pdfnoodle = new PdfNoodle('pdfnoodle_api_...');
You can also set the PDFNOODLE_API_KEY environment variable and omit the constructor argument:
const pdfnoodle = new PdfNoodle(); // reads from PDFNOODLE_API_KEY
Response Pattern
Every method returns a { data, error } object. When the request succeeds, data contains the result and error is null. When it fails, data is null and error contains the error details.
const { data, error } = await pdfnoodle.pdf.fromHTML({ html: '<h1>Hello</h1>' });
if (error) {
// error.name is a typed error code like "validation_error" or "rate_limit_exceeded"
console.error(error.name, error.message);
return;
}
// TypeScript knows data is not null here
console.log(data.signedUrl);
This pattern makes it impossible to forget error handling — you always destructure both values.
PDFs
The Pdf service generates PDFs from raw HTML or from reusable templates. Both sync methods include automatic polling — if the API takes longer than 30 seconds to render, the SDK polls the status endpoint with exponential backoff until the PDF is ready. No extra code needed.
Methods available:
| Method | Description |
|---|
pdf.fromHTML(payload, pollingOpts?) | Convert HTML to PDF. Auto-polls on 202. |
pdf.fromHTMLAsync(payload) | Convert HTML to PDF via webhook (no waiting). Requires webhook field. |
pdf.fromTemplate(payload, pollingOpts?) | Generate PDF from a reusable template. Auto-polls on 202. |
pdf.fromTemplateAsync(payload) | Generate PDF from template via webhook. Requires webhook field. |
pdf.getStatus(requestId) | Check the status of an async or queued generation (ONGOING / SUCCESS / FAILED). |
Generate PDF from HTML
Use pdf.fromHTML() to convert raw HTML into a PDF. The API renders the HTML with a headless browser and returns a temporary signed URL to download the file.
import { PdfNoodle } from 'pdfnoodle';
const pdfnoodle = new PdfNoodle('pdfnoodle_api_...');
const { data, error } = await pdfnoodle.pdf.fromHTML({
html: `
<html>
<head>
<style>
body { font-family: Arial, sans-serif; padding: 40px; }
h1 { color: #F55C3D; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f4f4f4; }
.total { font-weight: bold; font-size: 1.2em; }
</style>
</head>
<body>
<h1>Invoice #INV-2025-042</h1>
<p>Bill to: Acme Corp</p>
<table>
<tr><th>Item</th><th>Amount</th></tr>
<tr><td>Web Development</td><td>$2,500</td></tr>
<tr><td>Hosting (Annual)</td><td>$500</td></tr>
</table>
<p class="total">Total: $3,000</p>
</body>
</html>
`,
pdfParams: {
format: 'A4',
printBackground: true,
margin: { top: '20mm', bottom: '20mm', left: '15mm', right: '15mm' },
},
});
if (error) {
console.error('PDF generation failed:', error.message);
return;
}
console.log('Download your PDF:', data.signedUrl);
// => https://pdforge-production.s3.us-east-2.amazonaws.com/...
console.log('File size:', data.metadata.fileSize);
// => "4.066 kB"
What happens under the hood:
- The SDK sends a
POST to /v1/html-to-pdf/sync with your HTML and PDF parameters
- If the API responds with
200, the signed URL is returned immediately
- If it responds with
202 (rendering took >30s), the SDK automatically polls /v1/pdf/status/:requestId with exponential backoff until the PDF is ready
If you prefer to handle the result asynchronously via a webhook instead of waiting, use fromHTMLAsync() with a webhook field — you’ll get a requestId back immediately and the result will be POSTed to your URL when ready.
Generate PDF from Template
Use pdf.fromTemplate() when you have a reusable template created in the Template Builder. You pass the template ID and the dynamic data — the API merges them and returns the PDF.
This is ideal for documents you generate repeatedly with different data (invoices, receipts, certificates, reports).
import { PdfNoodle } from 'pdfnoodle';
const pdfnoodle = new PdfNoodle('pdfnoodle_api_...');
const { data, error } = await pdfnoodle.pdf.fromTemplate({
templateId: 'your-invoice-template-id',
data: {
invoice_number: 'INV-2025-042',
company_info: {
name: 'Acme Corp',
address_line_1: '123 Main Street, New York, NY',
},
items: [
{ description: 'Web Development', quantity: '1', price: '$2,500' },
{ description: 'Hosting (Annual)', quantity: '1', price: '$500' },
],
total: '$3,000',
due_date: 'March 1, 2025',
},
});
if (error) {
console.error('Failed:', error.message);
return;
}
console.log('Download your PDF:', data.signedUrl);
What happens under the hood:
- The SDK sends a
POST to /v1/pdf/sync with your template ID and data
- The API renders the template using Handlebars syntax, replacing
{{invoice_number}}, {{#each items}}, etc. with your values
- Same auto-polling behavior as
fromHTML() if rendering exceeds 30 seconds
Not sure which variables your template expects? Use pdfnoodle.templates.getVariables(templateId) to get the schema — see the Templates section below.
Templates
The Templates service lets you manage your reusable PDF templates programmatically. You can create templates using AI, list existing ones, fetch their details, and inspect their variable schemas.
Methods available:
| Method | Description |
|---|
templates.create({ prompt, displayName, fileUrl? }) | Create a template using AI. Optionally pass a reference document URL. |
templates.get(templateId) | Get a template’s details, or check creation status for AI-generated templates. |
templates.list() | List all templates in your account. |
templates.getVariables(templateId) | Get the variable schema a template expects (field names and types). |
Create a template with AI
The create() method uses AI to generate a template from a natural language description. Template creation is asynchronous — the method returns a templateId immediately, and the template is ready after about 2 minutes.
import { PdfNoodle } from 'pdfnoodle';
const pdfnoodle = new PdfNoodle('pdfnoodle_api_...');
// Step 1: Start template creation
const { data: created, error: createError } = await pdfnoodle.templates.create({
prompt: 'Create a professional invoice template with company logo, billing address, itemized table with description/quantity/price columns, subtotal, tax, and total. Use a clean blue and white color scheme.',
displayName: 'Professional Invoice',
});
if (createError) {
console.error('Failed to create template:', createError.message);
return;
}
console.log('Template creation started:', created.templateId);
// => "a1b2c3d4e5"
// Step 2: Poll until ready (template creation takes ~2 minutes)
let template;
while (true) {
const { data } = await pdfnoodle.templates.get(created.templateId);
// Check if it's still being created (has a status field)
const status = data as Record<string, unknown>;
if (status?.status === 'SUCCESS') {
console.log('Template is ready!');
break;
}
if (status?.status === 'FAILED') {
console.error('Template creation failed');
return;
}
// Still creating, wait 15 seconds
await new Promise((resolve) => setTimeout(resolve, 15000));
}
// Step 3: Check what variables the template expects
const { data: variables } = await pdfnoodle.templates.getVariables(created.templateId);
console.log('Template variables:', JSON.stringify(variables, null, 2));
// => { "invoice_number": "string", "items": [{ "description": "string", ... }], ... }
// Step 4: List all your templates
const { data: list } = await pdfnoodle.templates.list();
console.log('All templates:', list.templates);
// => [{ id: "a1b2c3d4e5", displayName: "Professional Invoice" }, ...]
The Tools service provides PDF utility operations — merging, splitting, compressing, converting Markdown, and updating metadata. Each tool accepts a publicly accessible PDF URL and returns a new signed URL with the result.
Methods available:
| Method | Description |
|---|
tools.getSignedUploadUrl(fileName?) | Get a presigned URL to upload a PDF to pdf noodle’s storage. |
tools.mergePdfs({ urls, finalFilename?, expiration? }) | Merge 2+ PDFs into one. |
tools.splitPdf({ url, splitMode?, ranges?, interval? }) | Split a PDF by page ranges or intervals. |
tools.compressPdf({ url, compressLevel? }) | Compress a PDF. Levels: low (print-ready), medium (balanced), high (web). |
tools.markdownToPdf({ markdown, customCss?, pdfOptions? }) | Convert Markdown to a styled PDF. |
tools.updatePdfMetadata({ url, metadata }) | Update a PDF’s title, author, keywords, and other metadata fields. |
Merge multiple PDFs
Combine multiple PDF files into a single document. The PDFs are merged in the order you provide them.
import { PdfNoodle } from 'pdfnoodle';
const pdfnoodle = new PdfNoodle('pdfnoodle_api_...');
const { data, error } = await pdfnoodle.tools.mergePdfs({
urls: [
'https://example.com/cover-page.pdf',
'https://example.com/chapter-1.pdf',
'https://example.com/chapter-2.pdf',
'https://example.com/appendix.pdf',
],
finalFilename: 'complete-report.pdf',
expiration: 7200, // signed URL valid for 2 hours
});
if (error) {
console.error('Merge failed:', error.message);
return;
}
console.log('Merged PDF:', data.url);
// => https://s3.amazonaws.com/.../complete-report.pdf
console.log('Valid until:', data.urlValidUntil);
Polling Options
The fromHTML() and fromTemplate() methods accept an optional second argument to configure polling behavior when the API returns a 202:
const { data, error } = await pdfnoodle.pdf.fromHTML(
{ html: '<h1>Large document</h1>' },
{
pollInterval: 2000, // initial delay between polls (default: 2000ms)
maxAttempts: 20, // max polls before timeout (default: 20)
backoffMultiplier: 1.5, // exponential backoff factor (default: 1.5)
maxPollInterval: 10000, // maximum delay cap (default: 10000ms)
signal: controller.signal, // AbortSignal to cancel polling
},
);
Cancelling a long-running generation
const controller = new AbortController();
// Cancel after 60 seconds
setTimeout(() => controller.abort(), 60000);
const { data, error } = await pdfnoodle.pdf.fromHTML(
{ html: '<h1>Hello</h1>' },
{ signal: controller.signal },
);
if (error?.name === 'polling_aborted') {
console.log('Generation was cancelled');
}
Error Codes
| Error Code | Description |
|---|
validation_error | Invalid request parameters (400) |
missing_api_key | No API key provided |
invalid_api_key | API key is not valid (401) |
not_found | Resource not found (404) |
rate_limit_exceeded | Too many requests (429) |
internal_server_error | Server error (500) |
pdf_generation_failed | PDF rendering failed |
polling_timeout | Polling exceeded max attempts |
polling_aborted | Polling was cancelled via AbortSignal |
Resources