docling.rs
v0.41.1
Published
Node.js / Bun bindings for docling.rs — a Rust port of docling. Convert Markdown, HTML, DOCX, PPTX, XLSX, PDF, images and more into a unified DoclingDocument (Markdown or docling-core JSON).
Maintainers
Readme
docling.rs (Node.js / Bun bindings)
Native Node.js / Bun bindings for
docling.rs — a Rust port of
docling. Convert Markdown, HTML,
DOCX, PPTX, XLSX, EPUB, ODF, LaTeX, email, PDF, images and more into a unified
DoclingDocument, and export it as Markdown or docling-core JSON.
Built with napi-rs, so it ships a real native addon (.node)
that loads in both Node.js and Bun (Bun implements N-API) — the same binary, no
rebuild between runtimes.
Install
Released versions ship prebuilt native binaries, so no Rust toolchain is needed to use the package:
npm install docling.rs # or: bun add docling.rsPrebuilt platforms: Linux x64 / arm64 (glibc) and Windows x64. (macOS isn't
prebuilt — build from source, see below.) The right binary is pulled in
automatically as a platform-specific optionalDependency (docling.rs-<triple>). Releases are published to npm by
manually running the npm publish workflow
(.github/workflows/npm-publish.yml) — by default it builds the latest master
(the workspace version); optionally pass a release tag to build that instead.
Decoupled from the crates.io release.
Build from source
This package lives in the docling.rs Cargo workspace and can also build the addon from Rust source — needed for local development or an unsupported platform. You need a Rust toolchain (1.82+) and Node.js 14+ (or Bun).
cd crates/docling-node
npm install # installs @napi-rs/cli
npm run build # release build → docling.rs.<platform>.node + native.js/.d.ts
# npm run build:debug # faster, unoptimizedThe addon statically links the ONNX runtime used by the PDF/image pipeline, so the built
.nodeis large. Declarative formats (Markdown, HTML, DOCX, …) don't touch it; only PDF/image conversion loads the ML models (downloaded on first use, like the CLI).
Quick start
import { convertFile, convert, DocumentConverter } from 'docling.rs'
// Convert a file — format detected from the extension.
const { content } = convertFile('report.docx')
console.log(content) // Markdown
// Convert in-memory bytes (e.g. an upload) — pass the format explicitly.
const md = convert({ name: 'notes', data: Buffer.from('# Hi\n'), format: 'md' })
// docling-core JSON instead of Markdown.
const json = convertFile('report.docx', { to: 'json' })
// Reuse a converter across many documents.
const converter = new DocumentConverter({ strict: true })
const a = converter.convert({ name: 'a.md', data: Buffer.from('# A\n') })CommonJS works too: const { convertFile } = require('docling.rs').
Async (off the event loop)
Conversion is CPU-bound; the *Async variants run it on the libuv thread pool
so the event loop stays free. Prefer these for PDF/image and for servers.
import { convertFileAsync } from 'docling.rs'
const res = await convertFileAsync('paper.pdf', { to: 'json' })Streaming Markdown
streamFileMarkdown yields Markdown chunks in document order as conversion
progresses. For PDF (whose pages convert in parallel) output starts flowing
before the whole document is done; concatenating the chunks reproduces the
buffered content byte-for-byte.
import { streamFileMarkdown } from 'docling.rs'
for await (const chunk of streamFileMarkdown('paper.pdf')) {
process.stdout.write(chunk)
}Chunking (docling's chunkers, for RAG)
chunkFile / chunk / chunkDocument (each with an …Async variant) run
docling's chunkers over a converted document and return embedding-ready
records. The default is the structure-driven hierarchical chunker (one
chunk per document item — whole lists, triplet-serialized tables — with its
heading path); pass chunker: 'hybrid' to refine against a token budget
(split oversized chunks, merge undersized same-heading neighbours), matching
docling's HybridChunker. The hybrid token counts come from a HuggingFace
tokenizer.json: pass a path via tokenizer, or omit it to use
models/chunk/tokenizer.json (all-MiniLM-L6-v2's — fetched by
scripts/download_dependencies.sh alongside the ML models, resolved through
the same install-home logic).
import { chunkFileAsync, Pipeline, chunkDocumentAsync } from 'docling.rs'
const chunks = await chunkFileAsync('report.docx', {
chunker: 'hybrid',
tokenizer: 'tokenizer.json', // e.g. all-MiniLM-L6-v2's
maxTokens: 256,
})
for (const c of chunks) {
await embed(c.contextualized) // heading path + text, ready for the embedder
}
// Chunk something you already converted (no re-conversion), e.g. a PDF
// that went through the warm Pipeline:
const { content } = new Pipeline().convertFile('paper.pdf', { to: 'json' })
const pdfChunks = await chunkDocumentAsync(content)Each Chunk is { text, headings?, docItems, contextualized } — docItems
holds the source items' JSON-pointer refs ("#/texts/12"), contextualized
is docling's contextualize() rendering to feed the embedding model.
Streaming chunks
streamFileChunks / streamChunks / streamDocumentChunks are the streaming
counterparts: async generators that yield each chunk as the chunkers produce
it — the first chunk is ready for embedding while the rest of the document
is still being chunked, and no all-chunks array is materialized. Abandoning
the generator early (break) cancels the background chunking.
import { streamFileChunks } from 'docling.rs'
for await (const c of streamFileChunks('report.docx', {
chunker: 'hybrid',
tokenizer: 'tokenizer.json',
maxTokens: 256,
})) {
await embed(c.contextualized) // embedding overlaps the remaining chunking
}PDF / images: getting the ML models
Declarative formats (Markdown, HTML, DOCX, XLSX, …) are pure Rust and need
nothing. The PDF/image path needs native assets that are not bundled in the
addon — pdfium plus the ONNX models (layout, OCR, TableFormer). Converting a
PDF/image/METS input throws until they're on disk. Fetch them with a
one-liner from your app's directory (where you'll npm install docling.rs):
curl -fsSL https://raw.githubusercontent.com/docling-project/docling.rs/master/scripts/install/download_dependencies.sh | shimport { convertFileAsync } from 'docling.rs'
const res = await convertFileAsync('paper.pdf', { to: 'markdown' }) // ✅ worksscripts/install/download_dependencies.sh fetches everything from this repo's
GitHub Releases straight into
./models and ./.pdfium — which this package (and the Rust CLI) look for by
default, relative to the process's current directory, so no env vars or setup
call are needed afterwards:
| Asset | Destination |
| --- | --- |
| pdfium | .pdfium/lib/libpdfium.so |
| layout (layout_heron.onnx) | models/layout_heron.onnx |
| OCR rec model + dictionary | models/ocr_rec.onnx, models/ppocr_keys_v1.txt |
| TableFormer | models/tableformer/{encoder,decoder,bbox}.onnx |
layout + TableFormer are PyTorch→ONNX exports (
docling-project/docling-layout-heron, Apache-2.0;docling-project/docling-models, CDLA-Permissive-2.0/Apache-2.0 — seedocs/MODELS_NOTICE.mdfor full attribution), not docling.rs's own weights — docling.rs hosts the converted.onnxas a GitHub Release purely so you don't need a local Python/torch toolchain. pdfium and the OCR model are re-hosted, unmodified, from their own public releases, on the same host for convenience.Run it from wherever your app lives — the script only writes to
./modelsand./.pdfiumunder the current directory, e.g. in a container build step:cd /path/to/your/app && curl -fsSL https://raw.githubusercontent.com/docling-project/docling.rs/master/scripts/install/download_dependencies.sh | shTo use your own export/host instead, point the env vars at it directly:
DOCLING_LAYOUT_ONNX,DOCLING_OCR_REC_ONNX,DOCLING_OCR_DICT,DOCLING_TABLEFORMER_{ENCODER,DECODER,BBOX},PDFIUM_DYNAMIC_LIB_PATH— an env var always wins over the./models/./.pdfiumdefault.
checkDependencies() // { home, pdfium, layout, ocr, tableformer, ready, missing }Reusing a warm Pipeline (many PDFs)
The one-shot convertFile / convertFileAsync rebuild the pipeline — reloading
every ONNX model — on each call. To convert many PDFs/images, reuse a Pipeline
so the models load once:
import { Pipeline } from 'docling.rs'
const pipeline = new Pipeline({ strict: true })
for (const path of pdfPaths) {
const { content } = await pipeline.convertFileAsync(path, { to: 'json' }) // warm models, off the event loop
}
// Or stream a PDF's Markdown as pages finish converting:
for await (const chunk of pipeline.streamFileMarkdown('paper.pdf')) {
process.stdout.write(chunk)
}Pipeline handles pdf and image inputs (the ML pipeline). The sync
convertFile / convert block the event loop; the *Async variants run on the
libuv thread pool, and streamFileMarkdown yields Markdown chunks in document
order as pages finish. Conversions on one instance run one at a time (the
models are mutable sessions) — overlapping *Async calls queue in submission
order, so batch throughput comes from keeping the models warm, not from
parallel calls.
Images
Pick how pictures render in Markdown with imageMode:
// Inline, self-contained: 
convertFile('slides.pptx', { imageMode: 'embedded' })
// Referenced: links + the image bytes to write yourself.
const res = convertFile('slides.pptx', { imageMode: 'referenced', artifactsDir: 'assets' })
for (const img of res.images) {
await fs.writeFile(img.path, img.data) // e.g. assets/image_000000.png
}JSON output always embeds extracted images as data URIs.
API
Functions
| Function | Returns | Notes |
| --- | --- | --- |
| convertFile(path, options?) | ConvertResult | Detects format from the extension. |
| convert(input, options?) | ConvertResult | In-memory bytes ({ name, data, format? }). |
| convertFileAsync(path, options?) | Promise<ConvertResult> | Off the event loop. |
| convertAsync(input, options?) | Promise<ConvertResult> | Off the event loop. |
| streamFileMarkdown(path, options?) | AsyncGenerator<string> | Markdown chunks in document order. |
| chunkFile(path, options?) | Chunk[] | Convert + run docling's hierarchical/hybrid chunker. |
| chunk(input, options?) | Chunk[] | Same, over in-memory bytes. |
| chunkDocument(documentJson, options?) | Chunk[] | Chunk an already-converted docling JSON document. |
| chunkFileAsync / chunkAsync / chunkDocumentAsync | Promise<Chunk[]> | Off the event loop. |
| streamFileChunks / streamChunks / streamDocumentChunks | AsyncGenerator<Chunk> | Chunks yielded as produced; break cancels. |
| supportedFormats() | string[] | Supported input format ids. |
| formatFromName(name) | string \| null | Detect a format id from a filename/extension. |
| checkDependencies(options?) | DependencyStatus | Report which PDF/image deps are present. |
Pipeline is the reusable warm PDF/image converter: new Pipeline(converterOptions)
then convertFile / convert / convertFileAsync / convertAsync /
convertFileStreaming / streamFileMarkdown.
DocumentConverter is the reusable form: new DocumentConverter(converterOptions)
then convert / convertFile / convertFileAsync / convertAsync /
convertFileStreaming. Converter config (strict, fetchImages,
allowedFormats) is set once on the constructor; output options (to,
imageMode, artifactsDir) are per call.
Options
to:"markdown"(default) or"json".imageMode:"placeholder"(default),"embedded", or"referenced".artifactsDir: directory name used inreferencedlinks (default"artifacts").strict: cleaner, more conformant Markdown instead of docling's byte-for-byte legacy output (Markdown only).fetchImages: for HTML/EPUB, resolve and embed external<img src>. Off by default; fetches http(s) URLs over the network — enable only for trusted input.allowedFormats: restrict the converter to these format ids/extensions.
ConvertResult
interface ConvertResult {
content: string // Markdown or JSON, per `to`
format: string // detected input format id
status: string // "success" | "partial_success" | "failure"
inputName: string
images: { path: string; data: Buffer }[] // for the `referenced` image mode
}Full TypeScript types are generated into index.d.ts / native.d.ts.
Examples
The examples/ folder is a self-contained project that depends on
the published docling.rs package — npm install there, then run any of them:
cd examples
npm install
node node-basic.mjs # ESM: file, bytes, JSON, reuse
bun run bun-basic.ts # Bun + TypeScript: async + streaming
node pdf-pipeline.mjs # warm Pipeline for PDFs (run scripts/install/download_dependencies.sh first)examples/node-basic.mjs— Node.js (ESM): file, bytes, JSON, reuse.examples/bun-basic.ts— Bun + TypeScript, with async and streaming.examples/pdf-pipeline.mjs— warmPipelinefor PDFs.
The smoke test exercises the locally-built addon instead: npm run build once at
the package root, then node test/smoke.mjs (or bun test/smoke.mjs).
License
MIT, same as the rest of docling.rs.
