> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pdfnoodle.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Node.js

> Official Node.js SDK for the pdf noodle API

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

<CodeGroup>
  ```bash npm theme={null}
  npm install pdfnoodle
  ```

  ```bash pnpm theme={null}
  pnpm add pdfnoodle
  ```

  ```bash yarn theme={null}
  yarn add pdfnoodle
  ```
</CodeGroup>

## Setup

```typescript theme={null}
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:

```typescript theme={null}
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.

```typescript theme={null}
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.

```typescript theme={null}
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:**

1. The SDK sends a `POST` to `/v1/html-to-pdf/sync` with your HTML and PDF parameters
2. If the API responds with `200`, the signed URL is returned immediately
3. 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](/knowledge-base/platform/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).

```typescript theme={null}
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:**

1. The SDK sends a `POST` to `/v1/pdf/sync` with your template ID and data
2. The API renders the template using [Handlebars](https://handlebarsjs.com/) syntax, replacing `{{invoice_number}}`, `{{#each items}}`, etc. with your values
3. Same auto-polling behavior as `fromHTML()` if rendering exceeds 30 seconds

<Tip>
  Not sure which variables your template expects? Use `pdfnoodle.templates.getVariables(templateId)` to get the schema — see the Templates section below.
</Tip>

***

## 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.

```typescript theme={null}
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" }, ...]
```

***

## Tools

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.

```typescript theme={null}
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`:

```typescript theme={null}
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

```typescript theme={null}
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

* [npm Package](https://www.npmjs.com/package/pdfnoodle) — Install and version history
* [GitHub Repository](https://github.com/pdfnoodle/pdfnoodle-node) — Source code and issues
* [API Reference](/api-reference) — Full REST API documentation
