Skip to content

Configuration Guide

All extraction behavior is controlled through ExtractionConfig. Pass it directly in code or load it from a TOML/YAML/JSON file. Every field is optional. For per-field documentation, see the Configuration Reference.

Python
import asyncio
from xberg import ExtractInput, extract, ExtractionConfig
async def main() -> None:
config = ExtractionConfig(
use_cache=True,
enable_quality_processing=True
)
result = await extract(ExtractInput(uri="document.pdf"), config)
print(result.results[0].content)
asyncio.run(main())

Three formats are supported. TOML is recommended.

Xberg rejects unrecognized keys in its extraction configuration tables. A misspelled nested setting fails loading instead of being ignored; use the Configuration Reference for the authoritative wire names. Settings inside url.crawl follow crawlberg’s configuration schema.

xberg.toml
use_cache = true
enable_quality_processing = true
[ocr]
backend = "tesseract"
language = "eng"

When no --config path is supplied, Xberg walks up from the current working directory looking for xberg.toml and uses the first match. If no project-local file is found, it falls back to a per-user global config at xberg/xberg.{toml,yaml,yml,json} in the platform config directory — $XDG_CONFIG_HOME (or ~/.config) on Linux, ~/Library/Application Support on macOS, and %APPDATA% on Windows. In the project walk, YAML and JSON files are supported only when passed explicitly via --config. If nothing is found, defaults are used.

Python
import asyncio
from xberg import ExtractInput, ExtractionConfig, extract
async def main() -> None:
config: ExtractionConfig = ExtractionConfig()
result = await extract(ExtractInput(uri="document.pdf"), config)
content: str = result.results[0].content
content_preview: str = content[:100]
print(f"Content preview: {content_preview}")
print(f"Total length: {len(content)}")
asyncio.run(main())

Automatic discovery is a CLI and server feature. The Node binding has no discovery helper, so read the file yourself and pass the parsed object as config:

config_discovery.ts
/// <reference types="node" />
import { existsSync, readFileSync } from "node:fs";
import { ExtractInputKind, extract, type ExtractionConfig } from "@xberg-io/xberg";
const input = {
kind: ExtractInputKind.Uri,
uri: "document.pdf",
};
const configPath = "xberg.json";
if (existsSync(configPath)) {
console.log("Found configuration file");
const config = JSON.parse(readFileSync(configPath, "utf8")) as ExtractionConfig;
const output = await extract(input, config);
console.log(output.results?.[0]?.content);
} else {
console.log("No configuration file found, using defaults");
const output = await extract(input);
console.log(output.results?.[0]?.content);
}

ExtractionConfig::apply_env_overrides() applies XBERG_* variables on top of an already-loaded config. Each variable that is set overrides the matching config-file value; unset variables are ignored. The serve and mcp commands call it automatically after loading the config. The extract and batch commands do not apply it — use flags or --config-json there.

Variable Overrides
XBERG_OCR_LANGUAGE OCR language (ISO 639 code, e.g. eng, deu)
XBERG_OCR_BACKEND OCR backend (tesseract, paddle-ocr, sceptre, vlm)
XBERG_DISABLE_OCR Disable OCR entirely (true/false)
XBERG_CHUNKING_MAX_CHARS Maximum characters per chunk
XBERG_CHUNKING_MAX_OVERLAP Overlap between chunks
XBERG_CHUNKING_TOKENIZER HuggingFace tokenizer model ID for token-based sizing
XBERG_CACHE_ENABLED Cache flag (true/false)
XBERG_TOKEN_REDUCTION_MODE Token reduction level (off, light, moderate, aggressive, maximum)
XBERG_OUTPUT_FORMAT Output format
XBERG_LAYOUT_PRESET Layout detection preset (fast, accurate)
XBERG_LLM_MODEL LLM model for structured extraction
XBERG_LLM_API_KEY API key for the structured-extraction LLM provider
XBERG_LLM_BASE_URL Custom base URL for the LLM provider
XBERG_VLM_OCR_MODEL VLM model for vision-based OCR
XBERG_VLM_EMBEDDING_MODEL LLM model for embedding generation
XBERG_EMBEDDING_PLUGIN_NAME Name of a registered in-process embedding backend

Server-only variables (XBERG_HOST, XBERG_PORT, XBERG_CORS_ORIGINS, XBERG_MAX_REQUEST_BODY_BYTES, XBERG_MAX_MULTIPART_FIELD_BYTES) configure the API/MCP server, not extraction.

For the extract and batch commands, sources are applied highest to lowest:

  1. Individual CLI flags (--ocr, --output-format, --chunk, …)
  2. Inline JSON (--config-json or --config-json-base64) — merged field by field, not whole-object
  3. Config file — explicit --config, otherwise the auto-discovered xberg.toml
  4. Built-in defaults

The serve and mcp commands add environment variables on top of the loaded config via apply_env_overrides(), so a set XBERG_* variable overrides the config-file value in those modes.

Two flags carry most of the cost and quality trade-off. use_cache reuses the result of an earlier extraction of the same input. enable_quality_processing cleans up the extracted text before it is returned:

Python
import asyncio
from xberg import ExtractInput, extract, ExtractionConfig
async def main() -> None:
config = ExtractionConfig(
use_cache=True,
enable_quality_processing=True
)
result = await extract(ExtractInput(uri="document.pdf"), config)
print(result.results[0].content)
asyncio.run(main())
Python
import asyncio
from xberg import ExtractInput, ExtractionConfig, OcrConfig, TesseractConfig, extract
async def main() -> None:
config: ExtractionConfig = ExtractionConfig(
ocr=OcrConfig(
backend="tesseract", language="eng+fra",
tesseract_config=TesseractConfig(psm=3)
)
)
result = await extract(ExtractInput(uri="document.pdf"), config)
print(result.results[0].content)
asyncio.run(main())

For backend selection and language packs, see OCR Guide. For fine-grained Tesseract tuning, see TesseractConfig Reference.

For Sceptre, use the backend name sceptre and place its sections directly in backend_options:

xberg.toml
[ocr]
backend = "sceptre"
language = ["eng", "deu"]
[ocr.backend_options.recognition]
batch_size = 4
[ocr.backend_options.concurrency]
max_threads = 2

Do not nest these options under backend_options.sceptre. Custom Rust builds use sceptre-ocr for the ONNX Runtime desktop/server backend or sceptre-ocr-tract for Android and iOS. WebAssembly support uses the opt-in Sceptre worker build/API because the model and tract runtime size are not part of the default wasm-target bundle. Use ocr.language for language selection; it overrides backend_options.model.languages. Leave backend_options.model.backend unset so Xberg can select ORT or tract for the target. The supported option paths are backend_options.detection.*, backend_options.recognition.*, backend_options.concurrency.max_threads, backend_options.model.cache_dir, backend_options.model.registry_owner, backend_options.model.detector_path, and backend_options.model.recognizer_path. cache_dir and automatic model download apply only to desktop/server ORT builds. Android and iOS exclude the downloader; both model paths are required and must reference application-resolved bundle or asset files.

Python
from xberg import (
ExtractionConfig,
ChunkingConfig,
EmbeddingConfig,
EmbeddingModelType,
)
config: ExtractionConfig = ExtractionConfig(
chunking=ChunkingConfig(
max_characters=1500,
overlap=200,
embedding=EmbeddingConfig(
model=EmbeddingModelType.preset("balanced")
),
)
)

ConcurrencyConfig.max_threads caps every internal thread pool at once: the global Rayon pool, ONNX Runtime intra-op threads, and the combined document/worker budget used by batch extraction.

When max_threads is left unset, the effective budget is min(detected_cpu_cores, 8) — not “use all available cores”. This 8-core ceiling is a deliberate serverless/shared-tenant default, not an auto-scaling target. On a host with more than 8 cores, the extra cores go unused by default:

  • Bare metal / VM with no CPU quota: max_threads must be set explicitly above 8 to use more than 8 cores. There is no other way to exceed the default ceiling on this class of host.
  • Linux containers under a cgroup CPU quota (e.g. Kubernetes resources.limits.cpu): the quota is used as the ceiling instead of the hardcoded 8, since the quota already reflects a deliberately-configured resource limit. This applies automatically; no configuration is needed.

If none of the above applies and the host has more than 8 cores, a one-time WARN-level log is emitted the first time the thread budget is resolved, naming the detected core count and the applied cap — so the ceiling is discoverable without reading source.

[concurrency]
max_threads = 32

ConcurrencyConfig.max_concurrent_ocr limits concurrent Tesseract recognition sessions on its own. When it is unset, recognition follows the thread budget, reduced to the number of sessions the host’s free memory holds. Set it when the host has cores to spare but not the memory to run a recognition session on each of them. The value is applied as given: neither max_threads nor the free-memory reading reduces it. The first extraction in a process fixes it for the rest of that process, and a later extraction that names a different value keeps the first one and logs a one-time WARN. See Concurrency & Scaling.

[concurrency]
max_threads = 32
max_concurrent_ocr = 8