iamgerwin/php-pdf-to-markdown-parser
A PHP package that converts PDF files into clean Markdown, extracting text and structure for documentation, notes, and LLM workflows. Works with Laravel and standalone PHP projects.
Install via Composer:
composer require iamgerwin/php-pdf-to-markdown-parser
Look at the README first for the exact entry-point class name (it has shifted across early releases — usually something like Iamgerwin\PdfToMarkdownParser\Parser or PdfToMarkdown) and the parse()/convert() method signature. The typical call looks like:
use Iamgerwin\PdfToMarkdownParser\Parser;
$parser = new Parser();
$markdown = $parser->parse(storage_path('app/docs/sample.pdf'));
Storage::put('docs/sample.md', $markdown);
The package is a thin wrapper around a pure-PHP PDF text extractor (the smalot/pdfparser ecosystem) with a heuristic layer that re-infers structure — headings from font size, lists from bullet glyphs, code from monospace fonts — into Markdown.
First use case to validate against: a vendor manual, README, or internal wiki export that you actually want converted. Drop it in, eyeball the output for heading levels and bullet nesting. If those two come out sane, the rest of the pipeline will usually work; if they don't, you're better off running smalot/pdfparser directly and doing your own structuring.
Wrap it in a dedicated service, not inline. The parser is stateful-ish and the conversion is I/O heavy; hide it behind a small class so you can swap implementations later:
final class PdfMarkdownConverter
{
public function __construct(private Parser $parser) {}
public function convert(string $absolutePath): string
{
return $this->parser->parse($absolutePath);
}
}
Queue big files. Conversion of anything above a few hundred KB can take seconds and eat memory. Dispatch a job:
class ConvertPdfToMarkdown implements ShouldQueue
{
public function __construct(public string $path) {}
public function handle(PdfMarkdownConverter $converter)
{
$md = $converter->convert(Storage::path($this->path));
Storage::put(str_replace('.pdf', '.md', $this->path), $md);
}
}
Bulk-convert with an Artisan command. Iterate a directory, skip already-converted files, log failures per file, and write .md alongside the source. This is the most common day-to-day shape — a one-off migration of a document dump to a docs repo or knowledge base.
Chain OCR before parsing when needed. For any PDF that looks scanned (or parses to near-empty output), pipe through ocrmypdf or pdftotext first, then feed the text-layer PDF into the parser. Do not try to bolt OCR into the parser itself.
Pair with a Markdown validator in your pipeline — league/commonmark round-trip or markdownlint in CI — because the heuristic output occasionally produces orphan headings or broken list nesting.
strlen($markdown) < 200 for a multi-page doc, route to OCR.#/## levels. Post-process if your corpus matters.memory_limit during extraction. Set memory_limit=-1 or raise it per job.mb_convert_encoding(..., 'UTF-8', 'UTF-8') and check for replacement characters.composer.json, and if you depend on it long-term, vendor the class locally or fall back to smalot/pdfparser + your own structuring — the wrapper logic is not much code and you'll want control over the heuristics anyway.->withHeadings(), ->withCodeBlocks()), they're usually method-chained before parse(), not constructor args. Read the README's method list — the fluent API has changed between minor versions.smalot/pdfparser's Page/Text objects. The wrapper exposes few hooks for that.How can I help you explore Laravel packages today?