Skip to content
Docs Security Sign in
PdfParse

PDF parse npm package for Node.js

The pdfparse-ai package on npm gives Node.js and TypeScript projects one client for the PdfParse API. It handles uploads from paths, buffers and streams, waits for long jobs, retries safely and ships its own type definitions, so your editor knows every field in the result.

Parse PDF

See the Node output before you write code

Drop a file and inspect the JSON, Markdown and CSV that your code will receive. The same structure comes back from the npm package.

Drop a PDF or scan

PDF, PNG or JPG up to 10 MB. Page one is parsed in full, right here.

Or try a sample file
Output

Optional. Comma separated field names, for example invoice_number, total, due_date.

Page one is parsed in your browser and never uploaded. No card needed to try page one.

What one call returns

Page one rendered with every block outlined, next to the JSON your code receives.

  1. Reading text layer
  2. Detecting layout
  3. Extracting tables
  4. Building JSON
Rendered page Page 1 of your document

Headings, paragraphs, tables and totals detected

heading
Invoice
paragraph
Harbor Street Joinery
14 Dock Lane
Portsmouth, NH 03801
key_value
Invoice no.INV-2026-0418IssuedOct 14, 2026DueNov 13, 2026
table
DescriptionQtyUnitAmount
Oak countertop, 2.4 m2640.001,280.00
Cabinet doors, shaker1285.001,020.00
On site fitting1450.00450.00
total
Subtotal2,750.00Tax 8.5%233.75Total dueUSD 2,983.75
paragraph
Payment by bank transfer within 30 days.
Quote the invoice number with your transfer.
Goods remain ours until paid in full.
footer
Harbor Street JoineryPage 1 of 2
Locked page

Pages 2 and later are parsed after you create an account.

Output
{
  "document": {
    "pages": 2,
    "parsed_pages": 1,
    "language": "en"
  },
  "pages": [
    {
      "number": 1,
      "blocks": [
        { "type": "heading", "text": "Invoice", "bbox": [48, 52, 132, 84] },
        { "type": "paragraph", "text": "Harbor Street Joinery, 14 Dock Lane, Portsmouth" },
        { "type": "key_value", "pairs": {
            "Invoice no.": "INV-2026-0418",
            "Issued": "Oct 14, 2026",
            "Due": "Nov 13, 2026" } },
        { "type": "table", "columns": ["Description", "Qty", "Unit", "Amount"],
          "rows": [
            ["Oak countertop, 2.4 m", 2, 640.00, 1280.00],
            ["Cabinet doors, shaker", 12, 85.00, 1020.00],
            ["On site fitting", 1, 450.00, 450.00] ] },
        { "type": "total", "label": "Total due", "value": 2983.75, "currency": "USD", "confidence": 0.98 }
      ]
    }
  ],
  "fields": {
    "invoice_number": { "value": "INV-2026-0418", "confidence": 0.99, "page": 1 },
    "total": { "value": 2983.75, "confidence": 0.98, "page": 1 },
    "due_date": { "value": "2026-11-13", "confidence": 0.97, "page": 1 }
  }
}

Downloads from the demo include a watermark field and cover page one only.

Page one is parsed in your browser, so your file never reaches our servers.

Request
The same request from your code
Read the API reference
$ curl https://api.pdfparse.ai/v1/parse \
  -H "Authorization: Bearer $PDFPARSE_API_KEY" \
  -F file[email protected] \
  -F outputs=json,markdown,csv
200 OK, 1 page parsed

Install from npm

Add the package with your usual tool and put the API key in an environment variable, never in the repository.

curl
npm install pdfparse-ai
# or: pnpm add pdfparse-ai, yarn add pdfparse-ai
export PDFPARSE_API_KEY="pp_live_your_key"

First call in ESM

In an ES module project the import is a single line. client.parse uploads the file and resolves when the result is ready.

TypeScript
import { PdfParse } from "pdfparse-ai";

const client = new PdfParse({ apiKey: process.env.PDFPARSE_API_KEY });
const result = await client.parse({ file: "invoice.pdf", outputs: ["json", "csv"] });

console.log(result.tables[0].csv);
console.log(`Pages counted: ${result.usage.pages}`);

CommonJS projects

Older services that still use require get the same client. Wrap top level calls in an async function.

Node.js
const { PdfParse } = require("pdfparse-ai");

const client = new PdfParse({ apiKey: process.env.PDFPARSE_API_KEY });

async function main() {
  const result = await client.parse({ file: "statement.pdf", outputs: ["markdown"] });
  console.log(result.markdown);
}

main();

Typed results in TypeScript

The package includes declarations for every request option and response object. Blocks are a discriminated union on type, so narrowing on "table" or "checkbox" gives you the right fields without casts.

TypeScript
import { PdfParse, type ParseResult, type Block } from "pdfparse-ai";

const client = new PdfParse({ apiKey: process.env.PDFPARSE_API_KEY });

const result: ParseResult = await client.parse({
  file: "report.pdf",
  outputs: ["json"],
  pages: "1-5",
  ocr: "auto",
});

const headings: Block[] = result.pages.flatMap((p) =>
  p.blocks.filter((b) => b.type === "heading")
);

Streams and buffers

Large files do not need to sit in memory. Pass a readable stream from disk or from object storage, or a Buffer you already hold. A url option lets the API fetch a public or signed link itself.

Each plan caps a single file. Starter accepts 25 MB and 100 pages, Growth 50 MB and 300 pages, Scale 100 MB and 1,000 pages, Enterprise 200 MB and 2,000 pages.

TypeScript
import { createReadStream } from "node:fs";

const fromDisk = await client.parse({
  file: createReadStream("scans/batch-0912.tiff"),
  outputs: ["json"],
  ocr: "always",
});

const fromLink = await client.parse({
  url: "https://files.example.com/signed/contract.pdf",
  outputs: ["markdown"],
});

Schemas and typed fields

Define the fields you need once and reuse the schema id. Fields can be typed as string, number, date, money, boolean or array. Custom schemas start on Growth.

TypeScript
const schema = await client.schemas.create({
  name: "purchase_order",
  fields: [
    { name: "po_number", type: "string", description: "Purchase order number" },
    { name: "order_date", type: "date", description: "Date the order was issued" },
    { name: "total", type: "money", description: "Order total including tax" },
  ],
});

const po = await client.parse({ file: "po.pdf", outputs: ["json"], schemaId: schema.id });
const total = po.fields.total;
if (total.confidence < 0.8) await queueForReview(po.id, "total", total.page);

Chunks for search and chat features

On Growth and above, the chunking option splits a parsed document by section and keeps each piece under the token budget you set. Each chunk carries page_start, page_end and section_path, which is what you need to show a citation under an answer in your product.

Write the rows to the vector store you already run. The page numbers stay valid because they come from the source PDF, not from the Markdown.

TypeScript
const doc = await client.parse({
  file: "policy-manual.pdf",
  outputs: ["json"],
  chunking: { strategy: "section", max_tokens: 800 },
});

const rows = doc.chunks.map((c) => ({
  content: c.text,
  citation: `p. ${c.page_start}${c.page_end > c.page_start ? `-${c.page_end}` : ""}`,
  path: c.section_path.join(" / "),
}));

Check usage from code

GET /v1/usage is exposed as client.usage.get(). It returns pages used, the allowance and the dates of the current billing period, which lets a scheduled job warn your team well before parsing pauses at the limit.

TypeScript
const usage = await client.usage.get();
const share = usage.pagesUsed / usage.pagesAllowed;
if (share > 0.85) await warnTeam(`PdfParse usage at ${Math.round(share * 100)} percent`);

Long jobs, polling and batches

By default the client waits for completion. Set wait to false to receive the job immediately and check it later, which keeps HTTP handlers and serverless functions short. On Growth and above, batches take up to 500 files with an idempotency key.

Run parallel work freely, jobs beyond what your account runs at once wait in the queue.

TypeScript
const job = await client.parse({ file: "archive.pdf", outputs: ["json"], wait: false });
const status = await client.jobs.get(job.id);

const batch = await client.batches.create({
  files: ["inv-001.pdf", "inv-002.pdf", "inv-003.pdf"],
  outputs: ["json", "csv"],
  idempotencyKey: "invoices-2026-09",
});

Verify webhooks in Express

Webhooks are part of Growth and above. Each delivery for parse.completed or parse.failed carries a PdfParse-Signature header, an HMAC SHA256 of the raw request body made with your endpoint secret. Verify it against the raw bytes, before any JSON parsing, and compare in constant time.

Answer with a 2xx status quickly and do heavy work in a queue. Deliveries that fail are retried with backoff, so make your handler safe to run twice for the same job id.

TypeScript
import express from "express";
import { createHmac, timingSafeEqual } from "node:crypto";

const app = express();
const secret = process.env.PDFPARSE_WEBHOOK_SECRET!;

app.post("/webhooks/pdfparse", express.raw({ type: "application/json" }), (req, res) => {
  const received = Buffer.from(req.header("PdfParse-Signature") ?? "", "hex");
  const expected = createHmac("sha256", secret).update(req.body).digest();

  if (received.length !== expected.length || !timingSafeEqual(received, expected)) {
    return res.status(401).send("invalid signature");
  }

  const event = JSON.parse(req.body.toString("utf8"));
  if (event.type === "parse.completed") saveResult(event.data);
  if (event.type === "parse.failed") alertOps(event.data);
  res.sendStatus(200);
});

Error handling

Network errors and rate limit responses are retried with backoff inside the client. Other failures throw a PdfParseError with the HTTP status and a machine readable code.

TypeScript
import { PdfParseError } from "pdfparse-ai";

try {
  await client.parse({ file: "large.pdf", outputs: ["json"] });
} catch (err) {
  if (err instanceof PdfParseError && err.status === 402) {
    notifyBilling("Page allowance reached, parsing paused until upgrade");
  } else if (err instanceof PdfParseError && err.status === 413) {
    await client.parse({ file: "large.pdf", outputs: ["json"], pages: "1-100" });
  } else {
    throw err;
  }
}

FAQ

Does the package work in serverless functions?

Yes. Use wait set to false so the function returns the job id quickly, then receive the result through a webhook or a later call to client.jobs.get.

Which Node.js module systems are supported?

Both. Import it with ESM syntax or load it with require in CommonJS. Type definitions ship inside the package, so no separate types install is needed.

Can I run it in the browser?

Keep calls on the server. Your API key must stay secret, so send files from your backend, or let users upload to your server first and forward them to PdfParse.

How is this different from a local PDF text library?

Local libraries read the text layer of digital PDFs. The pdfparse-ai package returns layout, reading order, tables with merged cells, OCR for scans, schema fields and chunks with page references in one call.

Ship PDF parsing in your Node service today

Pick a plan, add the key to your environment and make the first call.