Extract text, tables, images, and metadata from 91+ file formats and 248 programming languages including PDF, Office documents, and images. PHP bindings with modern PHP 8.2+ support and type-safe API.
Install via Composer:
composer require kreuzberg/kreuzberg- PHP 8.0+ required
- Optional: ONNX Runtime version 1.22.x for embeddings support
- Optional: Tesseract OCR for OCR functionality
Extract text, metadata, and structure from any supported document format:
<?php
declare(strict_types=1);
/**
* Basic Document Extraction (OOP API)
*
* This example demonstrates the simplest way to extract text from a document
* using the object-oriented API.
*/
require_once __DIR__ . '/vendor/autoload.php';
use Kreuzberg\Kreuzberg;
$kreuzberg = new Kreuzberg();
$result = $kreuzberg->extractFile('document.pdf');
echo "Extracted Content:\n";
echo "==================\n";
echo $result->content . "\n\n";
echo "Metadata:\n";
echo "=========\n";
echo "Title: " . ($result->metadata->title ?? 'N/A') . "\n";
echo "Authors: " . (isset($result->metadata->authors) ? implode(', ', $result->metadata->authors) : 'N/A') . "\n";
echo "Pages: " . ($result->metadata->pageCount ?? 'N/A') . "\n";
echo "Format: " . $result->mimeType . "\n\n";
if (count($result->tables) > 0) {
echo "Tables Found: " . count($result->tables) . "\n";
foreach ($result->tables as $index => $table) {
echo "\nTable " . ($index + 1) . " (Page {$table->pageNumber}):\n";
echo $table->markdown . "\n";
}
}Most use cases benefit from configuration to control extraction behavior:
With OCR (for scanned documents):
<?php
declare(strict_types=1);
/**
* Basic OCR with Tesseract
*
* Extract text from scanned PDFs and images using Tesseract OCR.
*/
require_once __DIR__ . '/vendor/autoload.php';
use Kreuzberg\Kreuzberg;
use Kreuzberg\Config\ExtractionConfig;
use Kreuzberg\Config\OcrConfig;
$config = new ExtractionConfig(
ocr: new OcrConfig(
backend: 'tesseract',
language: 'eng'
)
);
$kreuzberg = new Kreuzberg($config);
$result = $kreuzberg->extractFile('scanned_document.pdf');
echo "OCR Extraction Results:\n";
echo str_repeat('=', 60) . "\n";
echo $result->content . "\n\n";
$multilingualConfig = new ExtractionConfig(
ocr: new OcrConfig(
backend: 'tesseract',
language: 'eng+fra+deu'
)
);
$kreuzberg = new Kreuzberg($multilingualConfig);
$result = $kreuzberg->extractFile('multilingual_scan.pdf');
echo "Multilingual OCR:\n";
echo str_repeat('=', 60) . "\n";
echo substr($result->content, 0, 500) . "...\n\n";
$imageConfig = new ExtractionConfig(
ocr: new OcrConfig(
backend: 'tesseract',
language: 'eng'
)
);
$kreuzberg = new Kreuzberg($imageConfig);
$imageFormats = ['png', 'jpg', 'tiff'];
foreach ($imageFormats as $format) {
$file = "scan.$format";
if (file_exists($file)) {
echo "Processing $file...\n";
$result = $kreuzberg->extractFile($file);
echo "Extracted " . strlen($result->content) . " characters\n";
echo "Preview: " . substr($result->content, 0, 100) . "...\n\n";
}
}
$languages = [
'spa' => 'Spanish document',
'fra' => 'French document',
'deu' => 'German document',
'ita' => 'Italian document',
'por' => 'Portuguese document',
'rus' => 'Russian document',
'jpn' => 'Japanese document',
'chi_sim' => 'Chinese (Simplified) document',
];
foreach ($languages as $lang => $description) {
$file = strtolower(str_replace(' ', '_', $description)) . '.pdf';
if (file_exists($file)) {
$config = new ExtractionConfig(
ocr: new OcrConfig(
backend: 'tesseract',
language: $lang
)
);
$kreuzberg = new Kreuzberg($config);
$result = $kreuzberg->extractFile($file);
echo "$description ($lang):\n";
echo " Characters extracted: " . mb_strlen($result->content) . "\n\n";
}
}
use function Kreuzberg\extract_file;
$config = new ExtractionConfig(
ocr: new OcrConfig(backend: 'tesseract', language: 'eng')
);
$result = extract_file('invoice_scan.pdf', config: $config);
echo "Invoice OCR:\n";
echo str_repeat('=', 60) . "\n";
echo $result->content . "\n";
$result = $kreuzberg->extractFile('scanned.pdf');
$contentLength = strlen($result->content);
$pageCount = $result->metadata->pageCount ?? 1;
$avgCharsPerPage = $contentLength / $pageCount;
echo "\nOCR Quality Assessment:\n";
echo "Total characters: $contentLength\n";
echo "Pages: $pageCount\n";
echo "Average chars/page: " . number_format($avgCharsPerPage) . "\n";
if ($avgCharsPerPage < 100) {
echo "Warning: Low character count may indicate poor scan quality\n";
echo "Consider using image preprocessing or higher DPI settings.\n";
} elseif ($avgCharsPerPage > 2000) {
echo "Pass: Good - Adequate text extracted\n";
} else {
echo "Pass: Moderate - Text extracted successfully\n";
}See Table Extraction Guide for detailed examples.
<?php
declare(strict_types=1);
/**
* Batch Document Processing
*
* Process multiple documents in parallel for maximum performance.
* Kreuzberg's batch API uses multiple threads to extract documents concurrently.
*/
require_once __DIR__ . '/vendor/autoload.php';
use Kreuzberg\Kreuzberg;
use Kreuzberg\Config\ExtractionConfig;
use function Kreuzberg\batch_extract_files;
use function Kreuzberg\batch_extract_bytes;
$files = [
'document1.pdf',
'document2.docx',
'document3.xlsx',
'presentation.pptx',
];
$files = array_filter($files, 'file_exists');
if (!empty($files)) {
echo "Processing " . count($files) . " files in batch...\n\n";
$start = microtime(true);
$results = batch_extract_files($files);
$elapsed = microtime(true) - $start;
echo "Batch extraction completed in " . number_format($elapsed, 3) . " seconds\n";
echo "Average: " . number_format($elapsed / count($files), 3) . " seconds per file\n\n";
foreach ($results as $index => $result) {
$filename = basename($files[$index]);
echo "$filename:\n";
echo " Content: " . strlen($result->content) . " chars\n";
echo " Tables: " . count($result->tables) . "\n";
echo " MIME: " . $result->mimeType . "\n\n";
}
}
$config = new ExtractionConfig(
extractTables: true,
extractImages: false
);
$kreuzberg = new Kreuzberg($config);
$pdfFiles = glob('*.pdf');
if (!empty($pdfFiles)) {
echo "Processing " . count($pdfFiles) . " PDF files...\n";
$start = microtime(true);
$results = $kreuzberg->batchExtractFiles($pdfFiles, $config);
$elapsed = microtime(true) - $start;
echo "Completed in " . number_format($elapsed, 2) . " seconds\n";
echo "Throughput: " . number_format(count($pdfFiles) / $elapsed, 2) . " files/second\n\n";
$totalChars = 0;
$totalTables = 0;
foreach ($results as $result) {
$totalChars += strlen($result->content);
$totalTables += count($result->tables);
}
echo "Total content: " . number_format($totalChars) . " characters\n";
echo "Total tables: $totalTables\n";
}
$uploadedFiles = [
['data' => file_get_contents('file1.pdf'), 'mime' => 'application/pdf'],
['data' => file_get_contents('file2.docx'), 'mime' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
];
$dataList = array_column($uploadedFiles, 'data');
$mimeTypes = array_column($uploadedFiles, 'mime');
$results = batch_extract_bytes($dataList, $mimeTypes);
echo "\nProcessed " . count($results) . " files from memory\n";
function processDirectory(string $dir, Kreuzberg $kreuzberg): array
{
$results = [];
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir)
);
$files = [];
foreach ($iterator as $file) {
if ($file->isFile()) {
$ext = strtolower($file->getExtension());
if (in_array($ext, ['pdf', 'docx', 'xlsx', 'pptx', 'txt'], true)) {
$files[] = $file->getPathname();
}
}
}
if (empty($files)) {
return $results;
}
$batches = array_chunk($files, 10);
foreach ($batches as $batchIndex => $batch) {
echo "Processing batch " . ($batchIndex + 1) . "/" . count($batches) . "...\n";
$batchResults = $kreuzberg->batchExtractFiles($batch);
$results = array_merge($results, $batchResults);
}
return $results;
}
$directory = './documents';
if (is_dir($directory)) {
echo "\nProcessing directory: $directory\n";
$results = processDirectory($directory, $kreuzberg);
echo "Processed " . count($results) . " files\n";
}
$mixedFiles = ['valid.pdf', 'nonexistent.pdf', 'another.docx'];
try {
$results = batch_extract_files($mixedFiles);
} catch (\Kreuzberg\Exceptions\KreuzbergException $e) {
echo "Batch processing error: " . $e->getMessage() . "\n";
}
$allFiles = glob('documents/*.{pdf,docx,xlsx}', GLOB_BRACE);
$batchSize = 5;
$batches = array_chunk($allFiles, $batchSize);
$totalProcessed = 0;
echo "\nProcessing " . count($allFiles) . " files in " . count($batches) . " batches...\n";
foreach ($batches as $index => $batch) {
$progress = (($index + 1) / count($batches)) * 100;
echo sprintf("\rProgress: %.1f%% [%d/%d batches]",
$progress, $index + 1, count($batches));
$results = $kreuzberg->batchExtractFiles($batch);
$totalProcessed += count($results);
}
echo "\n\nCompleted! Processed $totalProcessed files.\n";- Installation Guide - Platform-specific setup
- API Documentation - Complete API reference
- Examples & Guides - Full code examples and usage guides
- Configuration Guide - Advanced configuration options
91+ file formats across 8 major categories with intelligent format detection and comprehensive metadata extraction.
| Category | Formats | Capabilities |
|---|---|---|
| Word Processing | .docx, .docm, .dotx, .dotm, .dot, .odt |
Full text, tables, images, metadata, styles |
| Spreadsheets | .xlsx, .xlsm, .xlsb, .xls, .xla, .xlam, .xltm, .xltx, .xlt, .ods |
Sheet data, formulas, cell metadata, charts |
| Presentations | .pptx, .pptm, .ppsx, .potx, .potm, .pot, .ppt |
Slides, speaker notes, images, metadata |
.pdf |
Text, tables, images, metadata, OCR support | |
| eBooks | .epub, .fb2 |
Chapters, metadata, embedded resources |
| Database | .dbf |
Table data extraction, field type support |
| Hangul | .hwp, .hwpx |
Korean document format, text extraction |
| Category | Formats | Features |
|---|---|---|
| Raster | .png, .jpg, .jpeg, .gif, .webp, .bmp, .tiff, .tif |
OCR, table detection, EXIF metadata, dimensions, color space |
| Advanced | .jp2, .jpx, .jpm, .mj2, .jbig2, .jb2, .pnm, .pbm, .pgm, .ppm |
OCR via hayro-jpeg2000 (pure Rust decoder), JBIG2 support, table detection, format-specific metadata |
| Vector | .svg |
DOM parsing, embedded text, graphics metadata |
| Category | Formats | Features |
|---|---|---|
| Markup | .html, .htm, .xhtml, .xml, .svg |
DOM parsing, metadata (Open Graph, Twitter Card), link extraction |
| Structured Data | .json, .yaml, .yml, .toml, .csv, .tsv |
Schema detection, nested structures, validation |
| Text & Markdown | .txt, .md, .markdown, .djot, .rst, .org, .rtf |
CommonMark, GFM, Djot, reStructuredText, Org Mode |
| Category | Formats | Features |
|---|---|---|
.eml, .msg |
Headers, body (HTML/plain), attachments, threading | |
| Archives | .zip, .tar, .tgz, .gz, .7z |
File listing, nested archives, metadata |
| Category | Formats | Features |
|---|---|---|
| Citations | .bib, .biblatex, .ris, .nbib, .enw, .csl |
Structured parsing: RIS (structured), PubMed/MEDLINE, EndNote XML (structured), BibTeX, CSL JSON |
| Scientific | .tex, .latex, .typst, .jats, .ipynb, .docbook |
LaTeX, Jupyter notebooks, PubMed JATS |
| Documentation | .opml, .pod, .mdoc, .troff |
Technical documentation formats |
| Feature | Description |
|---|---|
| Structure Extraction | Functions, classes, methods, structs, interfaces, enums |
| Import/Export Analysis | Module dependencies, re-exports, wildcard imports |
| Symbol Extraction | Variables, constants, type aliases, properties |
| Docstring Parsing | Google, NumPy, Sphinx, JSDoc, RustDoc, and 10+ formats |
| Diagnostics | Parse errors with line/column positions |
| Syntax-Aware Chunking | Split code by semantic boundaries, not arbitrary byte offsets |
Powered by tree-sitter-language-pack — documentation.
-
Text Extraction - Extract all text content with position and formatting information
-
Metadata Extraction - Retrieve document properties, creation date, author, etc.
-
Table Extraction - Parse tables with structure and cell content preservation
-
Image Extraction - Extract embedded images and render page previews
-
OCR Support - Integrate multiple OCR backends for scanned documents
-
Plugin System - Extensible post-processing for custom text transformation
-
Embeddings - Generate vector embeddings using ONNX Runtime models
-
Batch Processing - Efficiently process multiple documents in parallel
-
Memory Efficient - Stream large files without loading entirely into memory
-
Language Detection - Detect and support multiple languages in documents
-
Code Intelligence - Extract structure, imports, exports, symbols, and docstrings from 248 programming languages via tree-sitter
-
Configuration - Fine-grained control over extraction behavior
| Format | Speed | Memory | Notes |
|---|---|---|---|
| PDF (text) | 10-100 MB/s | ~50MB per doc | Fastest extraction |
| Office docs | 20-200 MB/s | ~100MB per doc | DOCX, XLSX, PPTX |
| Images (OCR) | 1-5 MB/s | Variable | Depends on OCR backend |
| Archives | 5-50 MB/s | ~200MB per doc | ZIP, TAR, etc. |
| Web formats | 50-200 MB/s | Streaming | HTML, XML, JSON |
Kreuzberg supports multiple OCR backends for extracting text from scanned documents and images:
-
Tesseract
-
Paddleocr
<?php
declare(strict_types=1);
/**
* Basic OCR with Tesseract
*
* Extract text from scanned PDFs and images using Tesseract OCR.
*/
require_once __DIR__ . '/vendor/autoload.php';
use Kreuzberg\Kreuzberg;
use Kreuzberg\Config\ExtractionConfig;
use Kreuzberg\Config\OcrConfig;
$config = new ExtractionConfig(
ocr: new OcrConfig(
backend: 'tesseract',
language: 'eng'
)
);
$kreuzberg = new Kreuzberg($config);
$result = $kreuzberg->extractFile('scanned_document.pdf');
echo "OCR Extraction Results:\n";
echo str_repeat('=', 60) . "\n";
echo $result->content . "\n\n";
$multilingualConfig = new ExtractionConfig(
ocr: new OcrConfig(
backend: 'tesseract',
language: 'eng+fra+deu'
)
);
$kreuzberg = new Kreuzberg($multilingualConfig);
$result = $kreuzberg->extractFile('multilingual_scan.pdf');
echo "Multilingual OCR:\n";
echo str_repeat('=', 60) . "\n";
echo substr($result->content, 0, 500) . "...\n\n";
$imageConfig = new ExtractionConfig(
ocr: new OcrConfig(
backend: 'tesseract',
language: 'eng'
)
);
$kreuzberg = new Kreuzberg($imageConfig);
$imageFormats = ['png', 'jpg', 'tiff'];
foreach ($imageFormats as $format) {
$file = "scan.$format";
if (file_exists($file)) {
echo "Processing $file...\n";
$result = $kreuzberg->extractFile($file);
echo "Extracted " . strlen($result->content) . " characters\n";
echo "Preview: " . substr($result->content, 0, 100) . "...\n\n";
}
}
$languages = [
'spa' => 'Spanish document',
'fra' => 'French document',
'deu' => 'German document',
'ita' => 'Italian document',
'por' => 'Portuguese document',
'rus' => 'Russian document',
'jpn' => 'Japanese document',
'chi_sim' => 'Chinese (Simplified) document',
];
foreach ($languages as $lang => $description) {
$file = strtolower(str_replace(' ', '_', $description)) . '.pdf';
if (file_exists($file)) {
$config = new ExtractionConfig(
ocr: new OcrConfig(
backend: 'tesseract',
language: $lang
)
);
$kreuzberg = new Kreuzberg($config);
$result = $kreuzberg->extractFile($file);
echo "$description ($lang):\n";
echo " Characters extracted: " . mb_strlen($result->content) . "\n\n";
}
}
use function Kreuzberg\extract_file;
$config = new ExtractionConfig(
ocr: new OcrConfig(backend: 'tesseract', language: 'eng')
);
$result = extract_file('invoice_scan.pdf', config: $config);
echo "Invoice OCR:\n";
echo str_repeat('=', 60) . "\n";
echo $result->content . "\n";
$result = $kreuzberg->extractFile('scanned.pdf');
$contentLength = strlen($result->content);
$pageCount = $result->metadata->pageCount ?? 1;
$avgCharsPerPage = $contentLength / $pageCount;
echo "\nOCR Quality Assessment:\n";
echo "Total characters: $contentLength\n";
echo "Pages: $pageCount\n";
echo "Average chars/page: " . number_format($avgCharsPerPage) . "\n";
if ($avgCharsPerPage < 100) {
echo "Warning: Low character count may indicate poor scan quality\n";
echo "Consider using image preprocessing or higher DPI settings.\n";
} elseif ($avgCharsPerPage > 2000) {
echo "Pass: Good - Adequate text extracted\n";
} else {
echo "Pass: Moderate - Text extracted successfully\n";
}Kreuzberg supports extensible post-processing plugins for custom text transformation and filtering.
For detailed plugin documentation, visit Plugin System Guide.
Generate vector embeddings for extracted text using the built-in ONNX Runtime support. Requires ONNX Runtime installation.
Process multiple documents efficiently:
<?php
declare(strict_types=1);
/**
* Batch Document Processing
*
* Process multiple documents in parallel for maximum performance.
* Kreuzberg's batch API uses multiple threads to extract documents concurrently.
*/
require_once __DIR__ . '/vendor/autoload.php';
use Kreuzberg\Kreuzberg;
use Kreuzberg\Config\ExtractionConfig;
use function Kreuzberg\batch_extract_files;
use function Kreuzberg\batch_extract_bytes;
$files = [
'document1.pdf',
'document2.docx',
'document3.xlsx',
'presentation.pptx',
];
$files = array_filter($files, 'file_exists');
if (!empty($files)) {
echo "Processing " . count($files) . " files in batch...\n\n";
$start = microtime(true);
$results = batch_extract_files($files);
$elapsed = microtime(true) - $start;
echo "Batch extraction completed in " . number_format($elapsed, 3) . " seconds\n";
echo "Average: " . number_format($elapsed / count($files), 3) . " seconds per file\n\n";
foreach ($results as $index => $result) {
$filename = basename($files[$index]);
echo "$filename:\n";
echo " Content: " . strlen($result->content) . " chars\n";
echo " Tables: " . count($result->tables) . "\n";
echo " MIME: " . $result->mimeType . "\n\n";
}
}
$config = new ExtractionConfig(
extractTables: true,
extractImages: false
);
$kreuzberg = new Kreuzberg($config);
$pdfFiles = glob('*.pdf');
if (!empty($pdfFiles)) {
echo "Processing " . count($pdfFiles) . " PDF files...\n";
$start = microtime(true);
$results = $kreuzberg->batchExtractFiles($pdfFiles, $config);
$elapsed = microtime(true) - $start;
echo "Completed in " . number_format($elapsed, 2) . " seconds\n";
echo "Throughput: " . number_format(count($pdfFiles) / $elapsed, 2) . " files/second\n\n";
$totalChars = 0;
$totalTables = 0;
foreach ($results as $result) {
$totalChars += strlen($result->content);
$totalTables += count($result->tables);
}
echo "Total content: " . number_format($totalChars) . " characters\n";
echo "Total tables: $totalTables\n";
}
$uploadedFiles = [
['data' => file_get_contents('file1.pdf'), 'mime' => 'application/pdf'],
['data' => file_get_contents('file2.docx'), 'mime' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
];
$dataList = array_column($uploadedFiles, 'data');
$mimeTypes = array_column($uploadedFiles, 'mime');
$results = batch_extract_bytes($dataList, $mimeTypes);
echo "\nProcessed " . count($results) . " files from memory\n";
function processDirectory(string $dir, Kreuzberg $kreuzberg): array
{
$results = [];
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir)
);
$files = [];
foreach ($iterator as $file) {
if ($file->isFile()) {
$ext = strtolower($file->getExtension());
if (in_array($ext, ['pdf', 'docx', 'xlsx', 'pptx', 'txt'], true)) {
$files[] = $file->getPathname();
}
}
}
if (empty($files)) {
return $results;
}
$batches = array_chunk($files, 10);
foreach ($batches as $batchIndex => $batch) {
echo "Processing batch " . ($batchIndex + 1) . "/" . count($batches) . "...\n";
$batchResults = $kreuzberg->batchExtractFiles($batch);
$results = array_merge($results, $batchResults);
}
return $results;
}
$directory = './documents';
if (is_dir($directory)) {
echo "\nProcessing directory: $directory\n";
$results = processDirectory($directory, $kreuzberg);
echo "Processed " . count($results) . " files\n";
}
$mixedFiles = ['valid.pdf', 'nonexistent.pdf', 'another.docx'];
try {
$results = batch_extract_files($mixedFiles);
} catch (\Kreuzberg\Exceptions\KreuzbergException $e) {
echo "Batch processing error: " . $e->getMessage() . "\n";
}
$allFiles = glob('documents/*.{pdf,docx,xlsx}', GLOB_BRACE);
$batchSize = 5;
$batches = array_chunk($allFiles, $batchSize);
$totalProcessed = 0;
echo "\nProcessing " . count($allFiles) . " files in " . count($batches) . " batches...\n";
foreach ($batches as $index => $batch) {
$progress = (($index + 1) / count($batches)) * 100;
echo sprintf("\rProgress: %.1f%% [%d/%d batches]",
$progress, $index + 1, count($batches));
$results = $kreuzberg->batchExtractFiles($batch);
$totalProcessed += count($results);
}
echo "\n\nCompleted! Processed $totalProcessed files.\n";For advanced configuration options including language detection, table extraction, OCR settings, and more:
Contributions are welcome! See Contributing Guide.
MIT License - see LICENSE file for details.
- Discord Community: Join our Discord
- GitHub Issues: Report bugs
- Discussions: Ask questions