> Source: https://txtfetch.com/for/javascript > Plain-text twin — every page on txtfetch.com has one. https://txtfetch.com/text --- # Four npm packages, one fetch() call. pdf-parse, mammoth, xlsx, tesseract.js: each one solid at its single job. None of them knows the other three exist. the-parser-zoo Node's document-parsing story is a shelf of single-purpose packages, not one library. Each package has a real limitation worth knowing before you build around it. pdf-parse is a thin wrapper around Mozilla's pdf.js, and it has not seen much maintenance. It gets plain text out of a normal PDF. But it has no OCR path, and it has no graceful handling for encrypted or malformed files. A corrupt PDF throws an error instead of degrading gracefully. mammoth converts .docx to plain text or HTML, but only .docx. It has nothing for .doc, .pptx, or .xlsx, and it deliberately drops images rather than describing them. xlsx (the ubiquitous SheetJS-derived package) reads spreadsheet cells across a wide format range. But formula evaluation depends on the workbook already carrying a cached result, the same trap as Python's openpyxl. Its API surface has also shifted across versions in ways that break upgrades. tesseract.js runs real OCR through a WASM build of Tesseract, which is genuinely impressive for a browser. But on a server it means downloading language-model data at cold start, a real problem in serverless. It also means running CPU-bound recognition on Node's single main thread, unless you wire up worker threads yourself. | library | covers | stops at | | --- | --- | --- | | `pdf-parse` | Digital-native PDF text via pdf.js | No OCR; throws rather than degrading on encrypted or malformed PDFs | | `mammoth` | .docx → plain text or HTML, style-aware | docx only — no .doc, .pptx, or .xlsx; images are dropped, not described | | `xlsx` | Spreadsheet cells across .xlsx/.xls/.csv and more | Formula values depend on a pre-existing cached result, same as Excel's own file format quirk | | `tesseract.js` | In-process OCR via a WASM Tesseract build | Downloads language data at cold start; CPU-bound recognition needs manual worker-thread wiring to avoid blocking | one-request txtfetch collapses the four-package stack into one endpoint. POST a file, or pass ?url=, and get back { "status": "success", "extracted\_text": "..." }. That is true regardless of whether the source needed pdf-parse's job, mammoth's, xlsx's, or tesseract.js's. The official @txtfetch/sdk wraps the same request in a zero-dependency TypeScript client, with typed errors. There is no per-format if/else needed to route a file to the right package before extraction can even start. JavaScript ```javascript import { readFile } from "node:fs/promises"; const file = new Blob([await readFile("report.pdf")]); const form = new FormData(); form.append("file", file, "report.pdf"); const res = await fetch("https://api.txtfetch.com/v1/extract", { method: "POST", headers: { Authorization: `Bearer ${process.env.TXTFETCH_KEY}` }, body: form, }); const { extracted_text } = await res.json(); console.log(extracted_text); ``` ``` { "status": "success", "extracted_text": "..." } ``` @txtfetch/sdk (JS/TS) npm install @txtfetch/sdk. Zero runtime dependencies, dual ESM/CJS, and it ships its own .d.ts. Install ```install npm install @txtfetch/sdk ``` Quickstart ```quickstart import { Txtfetch } from "@txtfetch/sdk"; // apiKey defaults to process.env.TXTFETCH_KEY const txtfetch = new Txtfetch(); const { extracted_text, metadata } = await txtfetch.extract({ file: "./whitepaper.pdf" }); console.log(extracted_text, metadata.chars); const byUrl = await txtfetch.extract({ url: "https://example.com/report.docx" }); console.log(byUrl.extracted_text); ``` from-a-url Skip the download entirely. Pass a `url` parameter and txtfetch fetches the document server-side: JavaScript ```javascript const endpoint = new URL("https://api.txtfetch.com/v1/extract"); endpoint.searchParams.set("url", "https://example.com/report.pdf"); const res = await fetch(endpoint, { method: "POST", headers: { Authorization: `Bearer ${process.env.TXTFETCH_KEY}` }, }); const { extracted_text } = await res.json(); console.log(extracted_text); ``` errors Every non-success response carries a stable `error.code`. Match on that, not on `error.message`. See the full [error reference](https://txtfetch.com/docs/errors) for every code and HTTP status txtfetch can return. JavaScript ```javascript import { readFile } from "node:fs/promises"; const file = new Blob([await readFile("report.pdf")]); const form = new FormData(); form.append("file", file, "report.pdf"); const res = await fetch("https://api.txtfetch.com/v1/extract", { method: "POST", headers: { Authorization: `Bearer ${process.env.TXTFETCH_KEY}` }, body: form, }); const body = await res.json(); if (res.ok) { console.log(body.extracted_text); } else if (res.status === 429) { console.log(`back off: ${body.error.code}, retry after ${res.headers.get("Retry-After")}s`); } else { console.log(`extraction failed: ${body.error.code} — ${body.error.message}`); } ``` big-files-and-batches Large uploads or slow documents are routed to an async job automatically. That returns a `202` plus a `job_id` to poll, and `?async=true` forces that path for any request. Direct upload size ceilings by plan: Hobby 10 MB, Developer 50 MB, Scale 200 MB. Use `?url=` for anything larger. Server-side fetches aren't held to the upload ceiling. See [async jobs & webhooks](https://txtfetch.com/docs/async) for the full lifecycle, including webhook delivery instead of polling. JavaScript ```javascript const headers = { Authorization: `Bearer ${process.env.TXTFETCH_KEY}` }; const submitUrl = new URL("https://api.txtfetch.com/v1/extract"); submitUrl.searchParams.set("url", "https://example.com/report.pdf"); submitUrl.searchParams.set("async", "true"); const submit = await fetch(submitUrl, { method: "POST", headers }); const { job_id } = await submit.json(); let result; do { await new Promise((resolve) => setTimeout(resolve, 2000)); const poll = await fetch(`https://api.txtfetch.com/v1/extract/${job_id}`, { headers }); result = await poll.json(); } while (result.status === "processing"); console.log(result.extracted_text); ``` gotchas - tesseract.js's default setup fetches its language traineddata files over the network, on first use. A serverless function gets a fresh container on every cold start. It re-downloads the language files every time, unless you bundle them and point at a local path. - pdf-parse hasn't seen substantial maintenance in years. Encrypted PDFs, and some malformed ones, throw an unhandled exception rather than a typed error you can catch and branch on. - mammoth's plain-text mode discards inline images entirely. That is correct behavior for a text-extraction step. But it means image-embedded diagrams inside a .docx contribute nothing to the output, silently. - xlsx's own maintainers have shipped breaking API changes across major versions. Pin the version deliberately, rather than accepting ^ ranges, in a document-ingestion pipeline you don't want to babysit. formats - [PDF](https://txtfetch.com/extract/pdf) - [Word / PowerPoint / Excel](https://txtfetch.com/extract/docx) - [Spreadsheets](https://txtfetch.com/extract/xlsx) - [Scans & images (OCR)](https://txtfetch.com/extract/image) faq **How do I parse a DOCX file in Node.js without mammoth?**: POST the .docx to https://api.txtfetch.com/v1/extract, or call txtfetch.extract({ file: "..." }) with the @txtfetch/sdk. You get back { "status": "success", "extracted_text": "..." }. The same call also handles .pptx, .xlsx, and legacy .doc, which mammoth doesn't. **Does tesseract.js need any setup to avoid slow cold starts?**: With txtfetch, there's no tesseract.js in your own deployment at all. OCR runs server-side on request. There is no WASM bundle to ship, and no language-model download to warm up on a fresh serverless container. **Why does my PDF parsing code throw on some files but not others?**: pdf-parse throws on encrypted or malformed PDFs rather than returning a typed error. txtfetch returns a structured { "error": { "code": "encrypted", ... } } or unsupported_format response instead. You can catch and branch on error.code, without a bare try/catch around a library's internals. **Is there an official JavaScript or TypeScript SDK?**: Yes. npm install @txtfetch/sdk. It has zero runtime dependencies. It ships both ESM and CJS builds, with .d.ts types. It requires Node ≥ 20. **Can I use txtfetch as a LangChain.js document loader?**: Yes. @txtfetch/langchain wraps the SDK as a TxtfetchLoader. Each file or URL becomes one Document, ready for a text splitter. See /docs/quickstarts for the install and a ten-line example. go-further - [@txtfetch/sdk (JS/TS) quickstart →](https://txtfetch.com/docs/quickstarts) - [Using txtfetch as a LangChain and LlamaIndex document loader →](https://txtfetch.com/blog/langchain-llamaindex-document-loader) - [Ingesting large documents and big batches →](https://txtfetch.com/blog/batch-and-large-document-ingestion) - [Search indexing pipelines →](https://txtfetch.com/solutions/search-indexing) - [API quickstart →](https://txtfetch.com/docs) - [How the pipeline works →](https://txtfetch.com/how-it-works) - [Get an API key →](https://app.txtfetch.com/signup) other-languages - [`Python`](https://txtfetch.com/for/python) - [`Go`](https://txtfetch.com/for/go) - [`Java`](https://txtfetch.com/for/java) - [`C# / .NET`](https://txtfetch.com/for/csharp) - [All languages →](https://txtfetch.com/for) ## Paste it into your project. The JavaScript snippet above runs as written. Add your key and it works. [Get an API key →](https://app.txtfetch.com/signup) [More SDK quickstarts →](https://txtfetch.com/docs/quickstarts)