PDF Tools

How to Convert PDF Pages to Images in Node.js

Build PDF page previews with Node.js using Imazeo’s API. Learn WebP and PNG output, page selection, response checks, and practical safeguards.

A white document page becomes a framed teal landscape image, connected by a green arrow on a warm cream and sage background.

A document preview sounds like a small feature until you have to build one. A user uploads a PDF, and the application needs a thumbnail for a file list, a preview in a review queue, or a quick look at a delivery note. Opening an entire PDF viewer for every card is often unnecessary. A rendered image is easier to show in a conventional image component.

In this guide we will turn the first page of a PDF into a WebP file with Node.js and Imazeo's PDF API. The example is intentionally small: one document, one page, one output file. After that, we'll look at formats, multiple pages, and the mistakes that tend to appear when this becomes a production feature.

When an image preview is useful

A PDF is still the right format when someone needs to download, print, or inspect the complete document. An image preview serves a different job: helping people recognize a file before opening it.

Consider an internal order dashboard. A row might show the original filename, upload date, and a preview of page one. The user can decide whether it is the right document without loading a full PDF viewer. The original PDF remains the source of truth; the preview is a derivative.

There is a trade-off. Rendering turns searchable text and vector graphics into pixels. A thumbnail is not a substitute for an accessible PDF viewer, text search, or the original download. Use it where an image genuinely improves the interface.

Choose the output before writing code

For a file list, the first page is often enough. For a document-review screen, you may need a few selected pages. Decide this before making requests, because converting every page when the UI shows only the first is wasted work.

Next, choose the image format. These are practical starting points, not strict rules:

Format

When to use it

Trade-off

WebP

Compact previews in modern web interfaces

Lossy settings can soften small text

PNG

Diagrams, screenshots, and pages where sharp detail matters

Files can be larger

JPEG

Photo-heavy documents and broadly compatible image exports

Compression artifacts may show around text

For this example we'll use WebP. If the document contains dense 8-point text or fine line drawings, compare a PNG at the same resolution before committing to WebP.

Render the first PDF page in Node.js

You'll need Node.js 24 or newer, a project and API key from your Imazeo workspace, and your deployment's API origin. Keep the API key on the server; never put it into browser JavaScript or a public repository.

Use a PDF you have permission to process. You can also download the synthetic two-page delivery note from the PDF documentation and save it as delivery-note.pdf in the same directory as the script.

Set IMAZEO_API_ORIGIN, IMAZEO_PROJECT_ID, and IMAZEO_API_KEY in the server environment. The API origin is not necessarily the marketing-site URL. Save the following as render-pdf.mjs:

javascript
import { readFile, writeFile } from "node:fs/promises";

const { IMAZEO_API_ORIGIN, IMAZEO_PROJECT_ID, IMAZEO_API_KEY } = process.env;
if (!IMAZEO_API_ORIGIN || !IMAZEO_PROJECT_ID || !IMAZEO_API_KEY) {
  throw new Error("Set IMAZEO_API_ORIGIN, IMAZEO_PROJECT_ID and IMAZEO_API_KEY");
}

const source = await readFile("delivery-note.pdf");
const form = new FormData();
form.append(
  "file",
  new Blob([source], { type: "application/pdf" }),
  "delivery-note.pdf"
);

const endpoint = new URL(
  `/v1/projects/${encodeURIComponent(IMAZEO_PROJECT_ID)}/tools/pdf/render`,
  IMAZEO_API_ORIGIN
);
endpoint.searchParams.set("pages", "1");
endpoint.searchParams.set("format", "webp");
endpoint.searchParams.set("scale", "1.5");

const response = await fetch(endpoint, {
  method: "POST",
  headers: { "X-API-Key": IMAZEO_API_KEY, Accept: "image/webp" },
  body: form,
  redirect: "error",
  signal: AbortSignal.timeout(120_000),
});

if (!response.ok) {
  const requestId = response.headers.get("x-request-id") ?? "unavailable";
  throw new Error(`Render failed (${response.status}); request ${requestId}`);
}

const type = (response.headers.get("content-type") ?? "")
  .split(";")[0].trim().toLowerCase();
if (type !== "image/webp") {
  throw new Error(`Unexpected response type: ${type || "missing"}`);
}

const image = Buffer.from(await response.arrayBuffer());
if (!image.length) throw new Error("The render returned an empty image");
await writeFile("delivery-note-page-1.webp", image, { flag: "wx" });
console.log(`Saved page 1 (${image.length} bytes)`);

Run node render-pdf.mjs. After the request succeeds, you'll have delivery-note-page-1.webp beside the script. The file is written only after the response status and content type are checked. The exclusive-write flag also prevents quietly overwriting an older preview.

The endpoint is POST /v1/projects/{projectId}/tools/pdf/render. It accepts a multipart PDF file and render options in the query string. For a single page, the response is image bytes, not a JSON wrapper. That's why the script uses response.arrayBuffer(). This behavior is documented in the Imazeo PDF API reference.

What does scale change?

The scale parameter changes the dimensions of the rendered result. A larger scale can make small text easier to read when the preview is opened at full size, but it also means more pixels to process, transfer, and store.

Imazeo documents a supported scale range of 0.5 to 2. Start with a modest scale, inspect the actual output, and increase it only if the preview needs more detail. Use the returned X-Image-Width and X-Image-Height headers when you need the real dimensions rather than guessing them from the PDF page size.

For a 200-pixel-wide thumbnail, an enormous source image offers little visual benefit. For a detail view, a very small output may look blurry. The correct setting follows the component's display size and the amount of text a person needs to read.

Requesting more than one page changes the response

There's an important API detail that's easy to miss. A request for pages=1 returns image bytes. A request for pages=1,2 returns a JSON result containing a data.pages array. Each item includes its page number, dimensions, content type, and base64-encoded image data.

That means a multi-page client must parse JSON, validate the page entries and content type, decode the base64 data, and write each file separately. Don't try to save the whole JSON response with a .png extension. Also note that base64 increases payload size in transit, so ask only for the pages your interface actually needs.

Page numbers start at 1. You can request specific pages such as 1,3, a complete start/end range, or the supported all-pages option. These selection modes are mutually exclusive, and rendering is limited by your deployment's configured caps. Check the current request limits rather than assuming a large PDF can be converted in a single call.

Check the preview at its real display size

Don't judge a preview solely by opening the exported file at 100% zoom. Put it into the actual UI and look for the details a user needs:

  • Text: Is the document title or reference number readable in the expanded preview?

  • Margins: Did the rendering preserve the page edges, or does the UI crop important content?

  • Orientation: Are portrait and landscape pages displayed without distortion?

  • Weight: Is the image reasonably sized for a scrolling list?

  • Fallback: If the PDF cannot be rendered, does the row still show a filename and a usable download action?

A familiar failure is using object-fit: cover on the full page preview. It can chop off headers and signatures. For document previews, showing the whole page with contain is often safer. Crop only when you deliberately want a decorative thumbnail and the page itself remains available elsewhere.

Handle failures as part of the feature

Real uploads are not always clean. A PDF can be encrypted, damaged, too large, or too complex for the available processing time. Imazeo rejects encrypted PDFs and enforces document and resource limits. A failed render should not leave a broken-image icon or a partially written output file in your UI.

Limit file size before upload, handle non-success status codes, and log a sanitized request ID for support. Treat invalid or encrypted files as input problems, not as errors to retry indefinitely. For temporary capacity problems, retry cautiously and respect the service's limits.

Rendered images can contain the same sensitive information as their PDFs. Keep API credentials on your server, restrict who can fetch private previews, and apply a deliberate retention policy. Imazeo's PDF tool response does not automatically create a permanently saved output in the project; keeping a result is a separate step. Review the output storage documentation if your application needs that behavior.

Try next

Start with one known PDF and generate its first-page preview. Compare WebP and PNG at the size you actually display, then add handling for multiple pages only when the UI requires it. You can explore the available PDF operations in the Imazeo PDF tools overview or work from the API documentation.