---
seo_title: Node.js File Conversion SDK | EnConvert npm Client
meta_desc: Install @enconvert/node-sdk from npm to convert files in Node.js 18+. Typed methods for url-to-pdf, image compression, anything-to-markdown, and anything-to-pdf.
keywords: file conversion sdk nodejs, npm file conversion api client, convert files nodejs, url to pdf nodejs sdk, heic to webp nodejs, docx to pdf node js, compress image nodejs, anything to markdown nodejs, anything to pdf nodejs, typescript conversion api client, enconvert node sdk
---

# Node.js File Conversion SDK

`@enconvert/node-sdk` is the official JavaScript / TypeScript client for converting URLs, images, and documents through the EnConvert API. Thirteen typed methods map 1:1 to REST endpoints like `POST /v1/convert/url-to-pdf`, and every single-file conversion method returns a presigned download URL. It targets Node.js 18+ with zero runtime dependencies, built on native `fetch`, `FormData`, and `node:stream`, and transparently recovers from reverse-proxy timeouts by polling job status. Ships dual ESM + CJS builds and full TypeScript declarations.

<div class="alert alert-info">
<strong>npm:</strong> <code>@enconvert/node-sdk</code> · <strong>Source:</strong> <a href="https://github.com/enconvert/node-sdk">enconvert/node-sdk</a> · <strong>Node:</strong> 18+
</div>

---

## Install

```bash
npm install @enconvert/node-sdk
```

```bash
pnpm add @enconvert/node-sdk
```

```bash
yarn add @enconvert/node-sdk
```

---

## Quick Start

```ts
import { Enconvert } from "@enconvert/node-sdk";

const client = new Enconvert({ apiKey: process.env.ENCONVERT_API_KEY! });

const result = await client.convertUrlToPdf("https://example.com", {
    saveTo: "page.pdf",
});

console.log(result.presignedUrl);
```

The SDK works in every modern Node runtime (Node 18+, Bun, Deno via the npm specifier). It is **server-side only**, so do not bundle your private API key into a browser app.

---

## Methods

The client exposes thirteen methods that map 1:1 to the REST API:

| Method | Endpoint | Returns |
|--------|----------|---------|
| `convertUrlToPdf(url, options?)` | `POST /v1/convert/url-to-pdf` | `ConversionResult` |
| `convertUrlToScreenshot(url, options?)` | `POST /v1/convert/url-to-screenshot` | `ConversionResult` |
| `convertUrlToMarkdown(url, options?)` | `POST /v1/convert/url-to-markdown` | `ConversionResult` |
| `convertImage(file, options)` | `POST /v1/convert/{from}-to-{to}` | `ConversionResult` |
| `convertDocument(file, options?)` | `POST /v1/convert/{from}-to-{to}` | `ConversionResult` |
| `compressImage(file, options?)` | `POST /v1/convert/compress-image` | `ConversionResult` |
| `convertToMarkdown(file, options?)` | `POST /v1/convert/anything-to-markdown` | `ConversionResult` |
| `convertToPdf(file, options?)` | `POST /v1/convert/anything-to-pdf` | `ConversionResult` |
| `getJobStatus(jobId)` | `GET /v1/convert/status/{jobId}` | `JobStatus` |
| `convertWebsiteToPdf(url, options?)` | `POST /v1/convert/website-to-pdf` | `BatchSubmission` |
| `convertWebsiteToScreenshot(url, options?)` | `POST /v1/convert/website-to-screenshot` | `BatchSubmission` |
| `getBatchStatus(batchId)` | `GET /v1/convert/batch/{batchId}` | `BatchStatus` |
| `waitForBatch(batchId, options?)` | `GET /v1/convert/batch/{batchId}` (polled) | `BatchStatus` |

Every method returns a typed promise. All option fields are optional unless marked otherwise. The last four are whole-site batch helpers: they submit and poll async jobs, so they return a `BatchSubmission` or a `BatchStatus` rather than a `ConversionResult`.

---

### `convertUrlToPdf`

Render any public URL to a PDF.

```ts
const result = await client.convertUrlToPdf("https://example.com", {
    pdfOptions: { pageSize: "A4", orientation: "landscape" },
    singlePage: false,
    viewportWidth: 1440,
    saveTo: "report.pdf",
});
```

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `saveTo` | `string` | -- | Local path to stream the PDF to. Parent directories are created automatically. |
| `singlePage` | `boolean` | `true` | `true` produces one continuous page. `false` paginates using `pdfOptions.pageSize`. |
| `pdfOptions` | `PdfOptions` | -- | Page size, orientation, margins, scale, grayscale, header/footer. See [PDF options](#pdf-options). |
| `viewportWidth` | `number` | `1920` | Browser viewport width in pixels. |
| `viewportHeight` | `number` | `1080` | Browser viewport height in pixels. |
| `loadMedia` | `boolean` | `true` | Wait for images and videos before capture. |
| `enableScroll` | `boolean` | `true` | Scroll top-to-bottom to trigger lazy loaders. |
| `outputFilename` | `string` | auto | Override the generated filename. `.pdf` is appended if missing. |

---

### `convertUrlToScreenshot`

Capture a full-page PNG of any URL.

```ts
const result = await client.convertUrlToScreenshot("https://example.com", {
    viewportWidth: 1440,
    saveTo: "screenshot.png",
});
```

Accepts the same viewport, media, scroll, and filename options as `convertUrlToPdf` (minus `singlePage` and `pdfOptions`).

---

### `convertUrlToMarkdown`

Extract clean GitHub-Flavored Markdown from any URL. The converter strips navigation, footers, ads, and scripts, keeps the main article body, and prepends YAML frontmatter (title, description, url, links, images).

```ts
const result = await client.convertUrlToMarkdown("https://example.com/article", {
    saveTo: "article.md",
});
```

Useful for building RAG pipelines, importing third-party content into a CMS, or generating training data.

---

### `convertImage`

Convert between `jpeg`, `png`, `svg`, `heic`, and `webp`.

```ts
// From a path
await client.convertImage("photo.heic", {
    outputFormat: "webp",
    saveTo: "photo.webp",
});

// From bytes
import { readFile } from "node:fs/promises";
const buf = await readFile("photo.heic");

await client.convertImage(
    { data: buf, filename: "photo.heic" },
    { outputFormat: "webp", saveTo: "photo.webp" },
);

// Rasterize an SVG at a fixed width
await client.convertImage("logo.svg", { outputFormat: "png", width: 512, saveTo: "logo.png" });
```

The input format is detected from the path / filename extension. The output format is required.

| Option | Type | Required | Description |
|--------|------|----------|-------------|
| `outputFormat` | `"jpeg" \| "png" \| "svg" \| "heic" \| "webp"` | Yes | Target format. |
| `saveTo` | `string` | -- | Local path to stream the result to. |
| `outputFilename` | `string` | -- | Override the generated filename. |
| `width` | `number` | -- | SVG input only (`svg-to-png`, `svg-to-jpeg`, `svg-to-webp`), 1 to 10000. On its own it scales proportionally, taking the height from the SVG's aspect ratio. |
| `height` | `number` | -- | SVG input only (`svg-to-png`, `svg-to-jpeg`, `svg-to-webp`), 1 to 10000. On its own it scales proportionally, taking the width from the SVG's aspect ratio. |

Set both `width` and `height` to pin an exact canvas, which may change the aspect ratio. Omit both and the output keeps the SVG's intrinsic width, height, or `viewBox`. Total output pixels are capped at 25,000,000. Neither option is accepted by `svg-to-heic`, and the SDK throws before sending the request if you pass them to any other conversion.

---

### `convertDocument`

Convert documents and data formats. The default `outputFormat` is `"pdf"`.

```ts
// docx -> pdf
await client.convertDocument("report.docx", { saveTo: "report.pdf" });

// json -> yaml
await client.convertDocument("data.json", {
    outputFormat: "yaml",
    saveTo: "data.yaml",
});

// markdown -> pdf with custom page setup
await client.convertDocument("README.md", {
    outputFormat: "pdf",
    pdfOptions: { pageSize: "A4", margins: { top: 20, bottom: 20, left: 25, right: 25 } },
    saveTo: "readme.pdf",
});
```

**Supported inputs:** `doc`, `docx`, `xls`, `xlsx`, `ppt`, `pptx`, `html`, `htm`, `odt`, `ods`, `odp`, `ots`, `pages`, `numbers`, `markdown` (`.md`, `.markdown`), `csv`, `json`, `xml`, `yaml` (`.yaml`, `.yml`), `toml`.

EPUB has no dedicated document conversion pair. Send `.epub` files through [`convertToPdf`](#converttopdf) or [`convertToMarkdown`](#converttomarkdown) instead.

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `outputFormat` | `string` | `"pdf"` | Target format. |
| `saveTo` | `string` | -- | Local path to stream the result to. |
| `outputFilename` | `string` | -- | Override the generated filename. |
| `pdfOptions` | `PdfOptions` | -- | Page setup. Only honored when output is PDF. |

---

### `compressImage`

Shrink a PNG, JPEG, or WebP without changing its format.

```ts
// Lossless pass only
const result = await client.compressImage("photo.jpg", { saveTo: "photo-small.jpg" });

// Aim for a 200 KB budget
const capped = await client.compressImage("photo.jpg", {
    targetSizeKb: 200,
    saveTo: "photo-capped.jpg",
});

console.log(capped.fileSize);
```

**Supported inputs:** `.png`, `.jpg`, `.jpeg`, `.webp`.

The output keeps the input format and extension, so there is no output format to choose. The first stage is lossless: metadata is stripped, the ICC profile and EXIF orientation are preserved, and the result is never larger than the input. Setting `targetSizeKb` adds a second stage that downscales with the aspect ratio locked until the budget is met. That target is best effort: an unreachable budget returns the smallest file achieved instead of an error, so check `result.fileSize`. Animated APNG and animated WebP are rejected with `400`, and the decoded canvas is capped at 40,000,000 pixels.

| Option | Type | Required | Description |
|--------|------|----------|-------------|
| `targetSizeKb` | `number` | -- | Size budget in KB, integer, minimum `1`. Omit it to run the lossless pass only. |
| `saveTo` | `string` | -- | Local path to stream the result to. |
| `outputFilename` | `string` | -- | Override the generated filename. The input extension is kept. |

---

### `convertToMarkdown`

Convert any supported document, spreadsheet, presentation, ebook, web, or plain-text file to Markdown.

```ts
await client.convertToMarkdown("handbook.docx", {
    saveTo: "handbook.md",
});
```

**Supported inputs (22):** `.csv`, `.doc`, `.docx`, `.epub`, `.htm`, `.html`, `.markdown`, `.md`, `.mdown`, `.mkd`, `.odp`, `.ods`, `.odt`, `.pdf`, `.ppt`, `.pptx`, `.rtf`, `.text`, `.txt`, `.xhtml`, `.xls`, `.xlsx`.

The output is a single heading-aware `.md` file built for RAG chunking: the document's heading hierarchy survives the conversion, so a semantic chunker can split on headings instead of arbitrary character counts. There are no PDF options on this endpoint. Any other extension throws before a request is made.

| Option | Type | Required | Description |
|--------|------|----------|-------------|
| `saveTo` | `string` | -- | Local path to stream the Markdown to. |
| `outputFilename` | `string` | -- | Override the generated filename. |

---

### `convertToPdf`

Convert any supported document, image, ebook, web, or plain-text file to PDF.

```ts
// docx -> pdf
await client.convertToPdf("contract.docx", { saveTo: "contract.pdf" });

// html -> pdf with full page geometry
await client.convertToPdf("invoice.html", {
    pdfOptions: { pageSize: "A4", margins: { top: 15, bottom: 15, left: 15, right: 15 } },
    saveTo: "invoice.pdf",
});

// pdf -> grayscale pdf (passthrough)
await client.convertToPdf("scan.pdf", {
    pdfOptions: { grayscale: true },
    saveTo: "scan-gray.pdf",
});
```

**Supported inputs (36):** `.bmp`, `.csv`, `.doc`, `.docx`, `.epub`, `.gif`, `.heic`, `.heif`, `.htm`, `.html`, `.jpeg`, `.jpg`, `.markdown`, `.md`, `.mdown`, `.mkd`, `.numbers`, `.odp`, `.ods`, `.odt`, `.ots`, `.pages`, `.pdf`, `.png`, `.ppt`, `.pptx`, `.rtf`, `.svg`, `.text`, `.tif`, `.tiff`, `.txt`, `.webp`, `.xhtml`, `.xls`, `.xlsx`.

A `.pdf` input is accepted and passed through, so with `pdfOptions: { grayscale: true }` this method doubles as a PDF normalize path. EPUB is handled here too, since it has no dedicated document conversion pair. Any other extension throws before a request is made.

<div class="alert alert-warning">
<strong>Geometry is input-dependent.</strong> Full page geometry (page size, page width and height, orientation, margins, scale, header, footer) is honored for HTML (<code>.html</code>, <code>.htm</code>, <code>.xhtml</code>), Markdown, plain text, EPUB, image, and SVG input. Office, ODF, iWork, RTF, and CSV input plus PDF passthrough support <code>grayscale</code> only, and return <code>400</code> if an explicit geometry option is set. <code>grayscale</code> itself is honored for every input.
</div>

| Option | Type | Required | Description |
|--------|------|----------|-------------|
| `saveTo` | `string` | -- | Local path to stream the PDF to. |
| `outputFilename` | `string` | -- | Override the generated filename. `.pdf` is appended if missing. |
| `pdfOptions` | `PdfOptions` | -- | Page setup. See the caveat above for which inputs honor geometry. |

---

### `getJobStatus`

Poll the status of an async or recovered job.

```ts
const status = await client.getJobStatus("job_abc123");

if (status.status === "success") {
    console.log(status.presignedUrl);
} else if (status.status === "failed") {
    console.error(status.error);
}
```

Returns `{ status: "processing" | "success" | "failed", presignedUrl?, objectKey?, error? }`.

<div class="alert alert-info">
<strong>You usually do not need to call this directly.</strong> The SDK polls automatically when a sync request returns 5xx. See <a href="#timeout-recovery">Timeout recovery</a> below.
</div>

---

## PDF Options

Passed via the `pdfOptions` field on `convertUrlToPdf`, `convertDocument`, and `convertToPdf`.

```ts
const result = await client.convertUrlToPdf("https://example.com", {
    pdfOptions: {
        pageSize: "A4",
        orientation: "landscape",
        margins: { top: 10, bottom: 10, left: 15, right: 15 },
        scale: 0.9,
        grayscale: false,
    },
    saveTo: "report.pdf",
});
```

| Field | Type | Description |
|-------|------|-------------|
| `pageSize` | `string` | `"A4"`, `"A3"`, `"Letter"`, `"Legal"`, etc. |
| `orientation` | `"portrait" \| "landscape"` | Defaults to portrait. |
| `margins` | `{ top, bottom, left, right }` (mm) | All four are optional. |
| `scale` | `number` | Render scale, e.g. `0.9` for 90%. |
| `grayscale` | `boolean` | Post-process the PDF through Ghostscript to grayscale. |
| `header` | `Record<string, string>` | Header text per page region. |
| `footer` | `Record<string, string>` | Footer text per page region. |

---

## Error handling

Errors are typed exception classes that you can match with `instanceof`.

```ts
import {
    Enconvert,
    APIError,
    AuthenticationError,
    RateLimitError,
} from "@enconvert/node-sdk";

try {
    await client.convertUrlToPdf("https://example.com");
} catch (e) {
    if (e instanceof AuthenticationError) {
        console.error("Invalid API key. Check ENCONVERT_API_KEY.");
    } else if (e instanceof RateLimitError) {
        console.error("Hit your monthly quota or per-second rate limit.");
    } else if (e instanceof APIError) {
        console.error(`API error [${e.statusCode}]: ${e.message}`);
    } else {
        throw e;
    }
}
```

| Class | Thrown on | Status code |
|-------|-----------|-------------|
| `AuthenticationError` | Invalid, missing, or revoked key | `401`, `403` |
| `RateLimitError` | Quota or rate limit exceeded | `429` |
| `APIError` | Any other 4xx / 5xx | the actual code |
| `EnconvertError` | Base class for all of the above | -- |

The full error message map is in the [Error Codes](/docs/error-codes) reference.

---

## Timeout recovery

Long URL-to-PDF or large document conversions can exceed the 60-120 second reverse-proxy timeout limit, even when the conversion eventually succeeds on the server. The SDK handles this transparently:

1. Before each request, the SDK generates a UUID and sends it as `job_id` in the request body.
2. If the original request returns 5xx, the SDK silently switches to polling `GET /v1/convert/status/{job_id}` every 3 seconds.
3. As soon as the job is recorded as `success`, the SDK returns the result. As soon as it is recorded as `failed`, the SDK throws `APIError`.
4. Polling deadline is 5 minutes. If exceeded, the SDK throws `APIError(504, "Conversion timed out")`.

You don't need to write any code for this, it just works. Set `timeout` on the constructor if you want to bound the initial request.

---

## Configuration

```ts
const client = new Enconvert({
    apiKey: process.env.ENCONVERT_API_KEY!,
    timeout: 300_000, // ms, 5 min default
    baseUrl: "https://api.enconvert.com", // override for self-hosted gateways
});
```

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `apiKey` | `string` | -- (required) | Private API key (`sk_live_...`). |
| `timeout` | `number` | `300_000` | Request timeout in ms. Aborts the underlying `fetch` via `AbortController`. |
| `baseUrl` | `string` | `https://api.enconvert.com` | API base URL. Trailing slashes are stripped. |

<div class="alert alert-warning">
<strong>Never hardcode the API key.</strong> Read it from an environment variable or your secret manager. Anyone who gets your private key can run conversions against your project's quota.
</div>

---

## Result shape

Every conversion method returns a `ConversionResult`:

```ts
interface ConversionResult {
    presignedUrl: string;          // signed URL to download the output (1 hour)
    objectKey: string;             // storage object key
    filename: string;              // server-side filename
    fileSize?: number;             // bytes
    conversionTimeSeconds?: number;
    jobId?: string;                // present when timeout recovery polled
}
```

The presigned URL is valid for one hour. If you need permanent access, download the file (use `saveTo`, or fetch the URL yourself) and store it in your own bucket.

---

## TypeScript

Type definitions ship with the package, so no `@types/...` install is needed. The package is dual-published (ESM + CJS) with proper `exports`, `types`, and `.d.ts` / `.d.cts` so it works under any Node module resolution mode.

```ts
import type {
    ClientOptions,
    CompressImageOptions,
    ConversionResult,
    ConvertDocumentOptions,
    ConvertImageOptions,
    ConvertToMarkdownOptions,
    ConvertToPdfOptions,
    FileInput,
    JobStatus,
    PdfOptions,
    UrlToMarkdownOptions,
    UrlToPdfOptions,
    UrlToScreenshotOptions,
} from "@enconvert/node-sdk";
```

---

## Upgrading

The package ships a small CLI, `enconvert-sdk`, for keeping itself current.

```bash
npx enconvert-sdk upgrade
```

```bash
npx enconvert-sdk upgrade --dry-run
```

```bash
npx enconvert-sdk version
```

`upgrade` detects npm, pnpm, yarn, or bun from the ambient package manager and always prints the exact install command before running it, so nothing happens to your lockfile unseen. `--dry-run` prints that command and stops. `version` reports the installed SDK version.

---

## Source and issues

- **npm:** [@enconvert/node-sdk](https://www.npmjs.com/package/@enconvert/node-sdk)
- **GitHub:** [enconvert/node-sdk](https://github.com/enconvert/node-sdk)
- **License:** MIT

---

## Frequently asked questions

### How do I convert files in Node.js with an npm package?

Install `@enconvert/node-sdk`, create a client with your API key (`new Enconvert({ apiKey: process.env.ENCONVERT_API_KEY! })`), and call a typed method like `convertUrlToPdf`, `convertImage`, or `convertDocument`. Pass `saveTo` to stream the result straight to disk.

### How do I convert HEIC to WebP in Node.js?

Call `convertImage` with the HEIC file (a path or a `{ data, filename }` buffer object) and `outputFormat: "webp"`. The SDK converts between `jpeg`, `png`, `svg`, `heic`, and `webp`; input format is detected from the filename extension.

### How do I compress an image in Node.js without changing its format?

Call `compressImage` with a `.png`, `.jpg`, `.jpeg`, or `.webp` file. The output keeps the input format and extension, strips metadata while preserving the ICC profile and EXIF orientation, and is never larger than the input. Add `targetSizeKb` to downscale toward a size budget; the target is best effort, so read `result.fileSize` to see what was actually achieved.

### How do I convert any document to Markdown for a RAG pipeline?

Call `convertToMarkdown` with the file and pass `saveTo` to write the `.md` straight to disk. It accepts 22 extensions across Office, OpenDocument, PDF, EPUB, HTML, CSV, and plain text, and returns one heading-aware Markdown file, so your chunker can split on the document's own headings instead of arbitrary character counts.

### How does the SDK handle long conversions that hit the reverse-proxy timeout?

Before each request the SDK generates a UUID and sends it as `job_id`; if the request returns 5xx, it silently polls `GET /v1/convert/status/{job_id}` every 3 seconds until the job is `success` or `failed`. The polling deadline is 5 minutes, after which it throws `APIError(504, "Conversion timed out")`.

### Can I use the Node.js SDK in a browser app?

No, the SDK is server-side only, because it authenticates with a private API key (`sk_live_...`) that must never be bundled into client-side code. It works in Node 18+, Bun, and Deno via the npm specifier.

### How long is the presigned download URL valid?

The `presignedUrl` in every `ConversionResult` is valid for one hour. For permanent access, download the file (use `saveTo`, or fetch the URL yourself) and store it in your own bucket.
