Extract text, tables, metadata, and images from 107 document formats (PDF, Office, images, HTML, email, archives, academic) using Xberg. Use when writing code that calls Xberg APIs in Python, Node.js/TypeScript, Rust, or CLI. Covers installation, extraction (sync/async), configuration (OCR, chunking, output format), batch processing, error handling, and plugins.
72
89%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
The canonical home for this skill is xberg in xberg-io/xberg
Xberg is a document intelligence library with a Rust core and bindings for Python, TypeScript/Node.js, Ruby, PHP, Go, Java, C#, Elixir, WebAssembly, Dart, Kotlin Android, Swift, Zig, and C. It extracts text, tables, metadata, and images from 107 formats across 140 unique file extensions and accepts 53 compatibility MIME aliases, including PDF, Office documents, images, HTML, email, archives, and academic formats.
Use this skill when writing code that:
If the
xbergMCP server is registered in this session, prefer its tools over shelling out to the CLI — they expose the same extraction surface with structured arguments and results.
pip install xbergnpm install @xberg-io/xbergcargo add xberg# Cargo.toml
[dependencies]
xberg = { version = "1.1.0", features = ["full"] }
tokio = { version = "1", features = ["full"] }
# feature flags: pdf, ocr, chunking, embeddings, language-detection, keywords, api, mcp
# (or "formats" / "full" aggregates); tokio-runtime is on by defaultbrew install xberg-io/tap/xberg
# or run without a persistent install (the CLI proxy package self-installs the binary):
npx @xberg-io/xberg-cli --help
uvx --from xberg-cli xberg --help
# or download a prebuilt binary from the latest GitHub release:
# https://github.com/xberg-io/xberg/releases/latest
# or build from source:
cargo install xberg-cliThe library entry points are extract(input, config) and extract_batch(inputs, config). Both return an ExtractionResult envelope — the extracted document(s) live in result.results, and per-document data (content, tables, metadata, …) is on each result.results[i]. Python and Node are async-only.
import asyncio
from xberg import ExtractInput, extract, ExtractionConfig
async def main() -> None:
result = await extract(ExtractInput(uri="document.pdf"), ExtractionConfig())
doc = result.results[0]
print(doc.content) # extracted text
print(doc.metadata) # document metadata
print(doc.tables) # extracted tables
asyncio.run(main())import { extract } from "@xberg-io/xberg";
const output = await extract({ kind: "uri", uri: "document.pdf" });
const doc = output.results[0];
console.log(doc.content);
console.log(doc.metadata);
console.log(doc.tables);use xberg::{extract, ExtractInput, ExtractionConfig};
#[tokio::main]
async fn main() -> xberg::Result<()> {
let output = extract(ExtractInput::from_uri("document.pdf"), &ExtractionConfig::default()).await?;
println!("{}", output.results[0].content);
Ok(())
}xberg extract document.pdf
xberg extract document.pdf --format json
xberg extract document.pdf --content-format markdownAll languages use the same configuration structure with language-appropriate naming conventions.
from xberg import (
ExtractInput, extract,
ExtractionConfig, OcrConfig, TesseractConfig, PdfConfig, ChunkingConfig, OutputFormat,
)
config = ExtractionConfig(
ocr=OcrConfig(
backend="tesseract",
language=["eng"],
tesseract_config=TesseractConfig(psm=6, enable_table_detection=True),
),
pdf_options=PdfConfig(passwords=["secret123"]),
chunking=ChunkingConfig(max_characters=1000, overlap=200),
output_format=OutputFormat("markdown"),
)
result = await extract(ExtractInput(uri="document.pdf"), config)import { extract, type ExtractionConfig } from "@xberg-io/xberg";
const config: ExtractionConfig = {
ocr: { backend: "tesseract", language: ["eng"] },
pdfOptions: { passwords: ["secret123"] },
chunking: { maxCharacters: 1000, overlap: 200 },
outputFormat: "markdown",
};
const output = await extract({ kind: "uri", uri: "document.pdf" }, config);use xberg::{extract, ExtractInput, ExtractionConfig, OcrConfig, ChunkingConfig, OutputFormat};
let config = ExtractionConfig {
ocr: Some(OcrConfig {
backend: "tesseract".into(),
language: vec!["eng".to_string()],
..Default::default()
}),
chunking: Some(ChunkingConfig {
max_characters: 1000,
overlap: 200,
..Default::default()
}),
output_format: OutputFormat::Markdown,
..Default::default()
};
let output = extract(ExtractInput::from_uri("document.pdf"), &config).await?;output_format = "markdown"
[ocr]
backend = "tesseract"
language = "eng"
[chunking]
max_characters = 1000
overlap = 200
[pdf_options]
passwords = ["secret123"]# CLI: auto-discovers xberg.toml/yaml/yml/json in current/parent directories
xberg extract doc.pdf
# or explicit:
xberg extract doc.pdf --config xberg.toml
xberg extract doc.pdf --config-json '{"ocr":{"backend":"tesseract","language":"deu"}}'extract_batch takes a list of ExtractInputs and returns one envelope whose results array holds a document per input (in input order); per-input failures are reported in result.errors.
from xberg import ExtractInput, extract_batch, ExtractionConfig
inputs = [
ExtractInput(uri="doc1.pdf"),
ExtractInput(uri="doc2.docx"),
ExtractInput(uri="doc3.xlsx"),
]
output = await extract_batch(inputs, ExtractionConfig())
for doc in output.results:
print(f"{len(doc.content)} chars extracted")import { extractBatch } from "@xberg-io/xberg";
const output = await extractBatch([
{ kind: "uri", uri: "doc1.pdf" },
{ kind: "uri", uri: "doc2.docx" },
]);
for (const doc of output.results) {
console.log(`${doc.content.length} chars`);
}use xberg::{extract_batch, ExtractInput, ExtractionConfig};
let config = ExtractionConfig::default();
let inputs = vec![ExtractInput::from_uri("doc1.pdf"), ExtractInput::from_uri("doc2.docx")];
let output = extract_batch(inputs, &config).await?;xberg batch *.pdf --format json
xberg batch docs/*.docx --content-format markdownOCR runs automatically for images and scanned PDFs. Tesseract is the default backend (native binding, no external install required).
Select with OcrConfig.backend:
"paddleocr" / "paddle-ocr"): ONNX-based PaddleOCR.OcrConfig.vlm_config).Custom backends can be registered in Python/Node via register_ocr_backend (see Advanced Features).
config = ExtractionConfig(ocr=OcrConfig(language=["eng"])) # English
config = ExtractionConfig(ocr=OcrConfig(language=["eng", "deu"])) # Multiple
# The single-string shorthand ("eng+deu") is only accepted in config files / --config-json,
# not in the OcrConfig constructor (Python takes a list, Node takes an array).config = ExtractionConfig(force_ocr=True) # OCR even if text is extractableextract / extract_batch return an ExtractionResult envelope: results (list of documents), errors (per-input failures), and summary (counts). Per-document fields live on each document in results — bind doc = result.results[0] (Python/Node) or &output.results[0] (Rust) first.
| Field | Python (doc.) | Node.js (doc.) | Rust (document.) | Description |
|---|---|---|---|---|
| Text content | content | content | content | Extracted text (str/String) |
| MIME type | mime_type | mimeType | mime_type | Input document MIME type |
| Metadata | metadata | metadata | metadata | Document metadata (flat mapping) |
| Tables | tables | tables | tables | Extracted tables with cells + markdown |
| Languages | detected_languages | detectedLanguages | detected_languages | Detected languages (if enabled) |
| Chunks | chunks | chunks | chunks | Text chunks (if chunking enabled) |
| Images | images | images | images | Extracted images (if enabled) |
| Elements | elements | elements | elements | Semantic elements (if element_based format) |
| Pages | pages | pages | pages | Per-page content (if page extraction enabled) |
| Keywords | extracted_keywords | extractedKeywords | extracted_keywords | Extracted keywords (if enabled) |
extract / extract_batch raise a plain RuntimeError on failure — the typed XbergError subclasses are not raised by these entry points, so catch RuntimeError. Per-input failures during extract_batch are reported non-fatally in result.errors.
from xberg import ExtractInput, extract, ExtractionConfig
try:
result = await extract(ExtractInput(uri="file.pdf"), ExtractionConfig())
for err in result.errors:
print(f"Per-input error: {err}")
except RuntimeError as e:
print(f"Extraction failed: {e}")The Node binding throws plain Error objects (it does not export typed error subclasses). Catch with instanceof Error, and inspect output.errors for non-fatal per-input failures.
import { extract } from "@xberg-io/xberg";
try {
const output = await extract({ kind: "uri", uri: "file.pdf" });
if (output.errors.length > 0) {
console.error("Per-input errors:", output.errors);
}
} catch (e) {
if (e instanceof Error) {
console.error(`Extraction failed: ${e.message}`);
}
}use xberg::{extract, ExtractInput, ExtractionConfig, XbergError};
let config = ExtractionConfig::default();
match extract(ExtractInput::from_uri("file.pdf"), &config).await {
Ok(output) => println!("{}", output.results[0].content),
Err(XbergError::Parsing { message, .. }) => eprintln!("Parse error: {message}"),
Err(XbergError::Ocr { message, .. }) => eprintln!("OCR error: {message}"),
Err(XbergError::UnsupportedFormat(mime)) => eprintln!("Unsupported: {mime}"),
Err(e) => eprintln!("Error: {e}"),
}extract / extract_batch return ExtractionResult with results, errors, and summary. Per-document fields (content, tables, chunks, …) are on result.results[i], NOT on the top-level return.await extract(...). Rust extract is async; use #[tokio::main] or an async context.ExtractInput, not a bare path. Use ExtractInput(uri=...) / ExtractInput::from_uri(...) (Python/Rust) or { kind: "uri", uri: "..." } (Node); for bytes use kind="bytes" with bytes/mime_type.max_characters and overlap (defaults 1000 / 200); these are also the readable attributes. When passing config as a dict/JSON, the max_chars / max_overlap aliases are also accepted. Node uses maxCharacters / overlap; Rust struct fields are max_characters / overlap.extract / extract_batch raise a plain RuntimeError on failure, not typed XbergError subclasses — catch RuntimeError. Node throws plain Error (no typed error subclasses).extract(input, &config) — the config is a reference. Use &ExtractionConfig::default() for defaults.--format controls CLI output (text, json, or toon). --content-format controls content rendering (plain, markdown, djot, html, json, or doctags).[chunking] fields are max_characters and overlap; other fields use names like output_format, pdf_options.| Category | Extensions |
|---|---|
.pdf | |
| Word | .docx, .docm, .doc, .dotx, .dotm, .dot, .odt, .pages, .wpd, .wp, .wp5, .wp6, .hwp, .hwpx |
| Spreadsheets | .xlsx, .xlsm, .xlsb, .xls, .xla, .xlam, .xltm, .xltx, .xlt, .ods, .numbers |
| Presentations | .pptx, .pptm, .ppt, .pps, .ppsx, .potx, .potm, .pot, .odp, .key |
| eBooks | .epub, .fb2 |
| Images | .png, .jpg, .jpeg, .gif, .webp, .bmp, .tiff, .tif, .jp2, .jpg2, .j2c, .j2k, .jpc, .jbig2, .jb2, .pnm, .pbm, .pgm, .ppm, .heic, .heics, .heif, .heifs, .hif, .avif, .avcs, .svg |
| Markup | .html, .htm, .xhtml, .xht, .xml, .kml |
| Data | .json, .geojson, .jsonl, .ndjson, .yaml, .yml, .toml, .csv, .tsv, .dbf, .sqlite, .sqlite3, .db, .gpkg, .gpkx |
| Text | .txt, .adoc, .asciidoc, .vtt, .md, .markdown, .commonmark, .qmd, .rmd, .mdx, .djot, .dj, .doctags, .rst, .org, .rtf |
.eml, .msg, .pst | |
| Archives | .zip, .tar, .tgz, .gz, .7z |
| Audio/Video | .mp3, .mpga, .m4a, .wav, .webm, .mp4, .mpg4, .mp4v, .m4v, .mpeg, .mpg, .mpe, .m1v, .m2v |
| Academic | .bib, .ris, .nbib, .enw, .tex, .latex, .typ, .typst, .jats, .nxml, .ipynb, .docbook, .dbk, .docbook4, .docbook5, .opml |
CSL JSON is supported through an explicit MIME type but does not have a registered file extension.
See references/supported-formats.md for the complete format reference with MIME types.
Detailed reference files for specific topics:
Task-focused sibling skills go deeper than this overview:
chunk command.embed command.batch command, --file-configs, parallelism, error recovery.--format / --content-format per consumer.Full documentation: https://docs.xberg.io GitHub: https://github.com/xberg-io/xberg
fbf07e1
Canonical home
since Sep 28, 2026
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.