Skip to content

Migrating from Kreuzberg v4

Xberg is the direct continuation of Kreuzberg. The Rust core and extraction API share the same lineage, but Xberg restarted package versioning at v1 under the new name. The Kreuzberg v4 line continues as a long-term-support release.

  • Existing v4 installs keep working. Kreuzberg v4 packages remain published under their original names and are supported as LTS (see below). Nothing you have deployed on v4 stops functioning.
  • Go module pins keep resolving. Old imports of github.com/kreuzberg-dev/kreuzberg continue to resolve from the Go module proxy cache. New v4 releases publish at github.com/kreuzberg-dev/kreuzberg-lts/v4; Xberg v1 is github.com/xberg-io/xberg/packages/go.
  • The v4 LTS line is MIT-licensed (earlier v4 shipped under Elastic License 2.0).

Kreuzberg v4 is maintained at kreuzberg-dev/kreuzberg-lts with docs at kreuzberg.dev. It receives critical bug and security fixes until the end of 2026, on a best-effort basis. No new features land on v4 — feature work happens in Xberg.

Stay on v4 LTS if you depend on the R binding (not shipped by Xberg v1 — v4 is the last line to ship it) or you are not ready to migrate.

Package identifiers moved from kreuzberg to xberg:

Ecosystem v4 (Kreuzberg) v1 (Xberg)
Rust (crates.io) kreuzberg xberg
Python (PyPI) kreuzberg xberg
npm @kreuzberg/node, @kreuzberg/wasm @xberg-io/xberg, @xberg-io/xberg-wasm
Maven dev.kreuzberg:kreuzberg io.xberg:xberg
NuGet Kreuzberg XbergIo.Xberg
Packagist kreuzberg/kreuzberg xberg-io/xberg
RubyGems kreuzberg xberg
Hex (Elixir) kreuzberg xberg
pub.dev (Dart) — xberg
Go module github.com/kreuzberg-dev/kreuzberg github.com/xberg-io/xberg/packages/go
R binding supported removed (use v4 LTS)

The standalone @kreuzberg/core npm package is gone — the Node package (@xberg-io/xberg) is self-contained.

Xberg standardizes extraction around an explicit input type and a result envelope:

  • Replace extract_file and extract_bytes with extract(ExtractInput, config).
  • Replace legacy batch variants with extract_batch(Vec<ExtractInput>, config).
  • Read extracted documents from ExtractionResult.results; per-input failures are retained in ExtractionResult.errors instead of discarding successful documents.

Generated bindings expose the same ExtractInput and ExtractionResult model with language-appropriate async wrappers. Other identifiers that changed:

  • Rust error type: KreuzbergError → XbergError (and Result<T> now aliases Result<T, XbergError>).
  • Config file discovery: kreuzberg.{toml,yaml,json} → xberg.{toml,yaml,yml,json} (the .yml extension is now also recognized).

Update the import/package name and see the API reference for your language.

All environment variables are re-prefixed KREUZBERG_* → XBERG_* — for example KREUZBERG_CACHE_DIR → XBERG_CACHE_DIR, KREUZBERG_OCR_BACKEND → XBERG_OCR_BACKEND, KREUZBERG_OUTPUT_FORMAT → XBERG_OUTPUT_FORMAT. Rename any KREUZBERG_-prefixed variables in your environment or deployment config.

  • Removed: KREUZBERG_PDFIUM_BUNDLED_PATH — the bundled PDFium fork was dropped.
  • New: Xberg adds XBERG_-prefixed variables for layout tuning, OCR model tier/version, CoreML, and the ONNX Runtime execution provider. See the configuration reference for the full list.

Downloaded models and the extraction cache move from the kreuzberg path segment to xberg (e.g. ~/.cache/kreuzberg → ~/.cache/xberg), and model repositories are pulled from the xberg-io Hugging Face org (was kreuzberg-dev). Existing caches are re-downloaded on first run.

The Python/torch EasyOCR backend from Kreuzberg is removed. Xberg 1.1 adds Sceptre, a Rust implementation of the same CRAFT and Gen2 CRNN architecture, not a drop-in EasyOCR API replacement. You can also use Tesseract, PaddleOCR, the pure-Rust Candle backend, or a VLM backend.

Kreuzberg v4 returned ocr_elements for OCR’d images without any setting. Xberg returns them only when you ask for them. To get back the word elements v4 returned, set include_elements to true and min_level to word in the OCR element configuration, and read ocr_elements from the first result. With the default min_level, line, Tesseract returns line elements but leaves out word elements; set min_level to word when you need each word’s coordinates.

Opt in to OCR elements to get each word’s text, bounding box and confidence

Python
import asyncio
from xberg import extract, ExtractInput, ExtractInputKind
from xberg._xberg import ExtractionConfig
async def main() -> None:
input = ExtractInput(kind=ExtractInputKind("uri"), mime_type="image/png", uri="https://example.com/images/test_hello_world.png")
config = ExtractionConfig.from_json("{\"force_ocr\":true,\"ocr\":{\"backend\":\"tesseract\",\"element_config\":{\"include_elements\":true,\"min_level\":\"word\"},\"enabled\":true,\"language\":[\"eng\"]}}")
result = await extract(input, config)
print(result.results[0].ocr_elements)
asyncio.run(main())

See OCR elements and word-level bounding boxes for every element field and filter.

In Kreuzberg v4, the Node package took the PaddleOCR settings as a typed object with camelCase fields. Xberg 1.3 and earlier took paddle_ocr_config as raw JSON. Every Xberg binding now also exposes typed paddle_ocr_settings through PaddleOcrConfig, with the field names that the language uses. A typed object has only the fields it declares, so the Node and TypeScript paddleOcrSettings object ignores any other property, as every other typed config does. The legacy paddle_ocr_config JSON field remains available, ignores unknown extension keys, and is deprecated for removal in Xberg 2.0; typed settings take precedence when both are set.

Config files, JSON configs, and the REST and MCP request bodies keep the snake_case keys, such as use_angle_cls and model_tier. In these, an unknown key in paddle_ocr_settings fails validation.

Configure PaddleOCR language, model tier, and model version

Python
import asyncio
from xberg import extract, ExtractInput, ExtractInputKind
from xberg._xberg import ExtractionConfig
async def main() -> None:
input = ExtractInput(kind=ExtractInputKind("uri"), mime_type="image/png", uri="https://example.com/images/test_hello_world.png")
config = ExtractionConfig.from_json("{\"ocr\":{\"backend\":\"paddleocr\",\"enabled\":true,\"language\":[\"en\"],\"paddle_ocr_settings\":{\"language\":\"en\",\"model_tier\":\"mobile\",\"model_version\":\"pp-ocrv6\"}}}")
result = await extract(input, config)
print(result.results[0].content)
asyncio.run(main())

See the installation guide for the current package names.