iammuttaqi / laravel-anydoc
Convert Word, PowerPoint, Excel, OpenDocument, RTF, EPUB, CSV, and PDF documents to clean GitHub-Flavored Markdown in Laravel, powered by Firecrawl anydoc.
Requires
- php: ^8.1
- illuminate/contracts: ^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
- symfony/process: ^6.4|^7.0|^8.0
Requires (Dev)
- laravel/pint: ^1.0
- orchestra/testbench: ^8.0|^9.0|^10.0|^11.0
- pestphp/pest: ^2.0|^3.0
- pestphp/pest-plugin-laravel: ^2.0|^3.0
- phpstan/phpstan: ^1.10|^2.0
README
Convert Word, PowerPoint, Excel, OpenDocument, RTF, EPUB, CSV, and PDF documents to clean GitHub-Flavored Markdown in Laravel — powered by Firecrawl anydoc, the Rust library that converts office documents to LLM-ready Markdown in single-digit milliseconds.
There is no PHP binding for anydoc, so this package drives its CLI through Symfony Process. The CLI ships as the npm package @firecrawl/anydoc; npx downloads the prebuilt binary on first use and caches it, so there is nothing else to install beyond Node.js.
Requirements
- PHP 8.1+
- Laravel 10, 11, 12 or 13
- Node.js 20+ (with
npxon PATH)
Installation
composer require iammuttaqi/laravel-anydoc
Optionally publish the config file:
php artisan vendor:publish --tag=anydoc-config
For a permanent install of the CLI (instead of going through npx on every call), run:
php artisan anydoc:install
Usage
use Muttaqi\Anydoc\Facades\Anydoc; use Muttaqi\Anydoc\Support\Format; // From a file path: $markdown = Anydoc::toMarkdown('/path/to/report.docx'); // From an uploaded file: $markdown = Anydoc::toMarkdown($request->file('document')); // From bytes, with the format detected from the content: $markdown = Anydoc::toMarkdownBytes($bytes); // Or name it, which signature-less formats (CSV) need: $markdown = Anydoc::toMarkdownBytes($bytes, Format::Csv); // Format detection: Anydoc::formatFromPath('report.odt'); // Format::Odt Anydoc::formatFromExtension('.pptm'); // Format::Pptx Anydoc::formatFromBytes($bytes); // Format::Docx, or null
Or via dependency injection:
use Muttaqi\Anydoc\Anydoc; class DocumentController { public function show(Anydoc $anydoc, string $id) { return $anydoc->toMarkdown('/path/to/'.$id.'.pptx'); } }
Supported formats
| Format | Extensions |
|---|---|
| Word | .doc, .docx, .docm |
| PowerPoint | .ppt, .pps, .pot, .pptx, .pptm, .ppsx, .ppsm |
| Excel | .xls, .xlsx, .xlsm, .xlsb |
| OpenDocument | .odt, .ods, .odp |
| Rich Text | .rtf |
| EPUB | .epub |
| CSV | .csv |
.pdf |
The format is detected from the file content, not the extension, so mislabeled files still convert correctly. CSV has no content marker — pass it explicitly (or rely on the file extension).
Artisan commands
# Convert a single document php artisan anydoc:convert report.docx php artisan anydoc:convert slides.pptx --output=slides.md php artisan anydoc:convert data.csv --format=csv --output=data.md # Install the anydoc CLI globally php artisan anydoc:install
Errors
A conversion only fails when no meaningful Markdown could come out of the file. Each failure is thrown as a typed exception, mirroring anydoc's own error variants:
| Exception | Meaning |
|---|---|
UnsupportedException |
Unknown format, or one that cannot be converted (an image-only PDF) |
MalformedException |
Structurally unusable document |
EncryptedException |
Encrypted or password-protected |
ResourceLimitException |
Crossed a fixed safety limit |
MissingPartException |
A part required for meaningful output is absent |
InvalidUsageException |
The CLI was invoked with bad arguments (should not happen) |
ConversionException |
Any other conversion failure |
BinaryNotFoundException |
Node/npx/the anydoc binary is not installed |
AnydocException |
Base class for all of the above |
use Muttaqi\Anydoc\Exceptions\AnydocException; use Muttaqi\Anydoc\Exceptions\EncryptedException; use Muttaqi\Anydoc\Exceptions\UnsupportedException; try { $markdown = Anydoc::toMarkdown($file); } catch (EncryptedException|UnsupportedException $e) { // No document comes out of these — record the file and move on. report($e); }
Configuration
Publish the config and adjust config/anydoc.php:
| Variable | Default | Description |
|---|---|---|
ANYDOC_USE_NPX |
true |
Use npx -y @firecrawl/anydoc instead of a fixed binary |
ANYDOC_BINARY |
anydoc |
Binary to call when ANYDOC_USE_NPX=false |
ANYDOC_NPX_PACKAGE |
@firecrawl/anydoc |
npm package providing the CLI |
ANYDOC_TIMEOUT |
60 |
Max seconds per conversion (seconds) |
ANYDOC_TEMP_DIR |
sys_get_temp_dir() |
Where temporary input files are written |
Windows
Symfony Process resolves npx through the shell-less PATH lookup, which can miss npx.cmd. If that happens, install the CLI and point the package at the full path:
npm install -g @firecrawl/anydoc
ANYDOC_USE_NPX=false ANYDOC_BINARY=C:\path\to\npx.cmd
Notes
- Embedded images render as their alt text (Markdown cannot embed bytes); the raw bytes stay available on anydoc's document model, which the CLI does not expose.
- Scanned/image-only PDFs need OCR, which anydoc does not do — they fail as
UnsupportedException. Firecrawl's hosted Parse API handles those (seefirecrawl/firecrawl-sdk). - Conversions are synchronous and spawn a short-lived process. For batch pipelines, queue the work.
Testing
composer test
Unit tests always run. Integration tests convert real documents and auto-skip when the anydoc CLI is unavailable.
License
MIT