davidbadura/markdown-builder
A fluent PHP library for building Markdown documents programmatically. Compose headings, paragraphs, lists, links, code blocks, and tables via a clean, chainable API, ideal for READMEs, reports, and dynamic docs in any PHP or Laravel project.
composer require davidbadura/markdown-builder
There is no Laravel service provider or facade to discover — it's a framework-agnostic PHP library. The practical setup is:
composer.json first to confirm the PHP constraint against your app's version.src/ before the README. The surface is tiny (a builder plus a handful of element/node classes), and the source will tell you the exact method names and whether output is produced by build(), __toString(), or both.AppServiceProvider — the builder accumulates state, so a singleton will leak content between requests/jobs.A first, realistic use case is generating a markdown report or changelog from data you already have:
use DavidBadura\MarkdownBuilder\MarkdownBuilder;
// Method names are illustrative — confirm the exact API in src/.
$md = (new MarkdownBuilder())
->addHeader('Weekly report', 1)
->addParagraph('Generated ' . now()->toDateString())
->addListing([
'New signups: ' . $signups,
'Churn: ' . $churn,
]);
Storage::disk('local')->put('reports/weekly.md', (string) $md);
Then serve it with response()->download(...) or Storage::download(...). That single flow — model data in, .md file out — is the highest-value entry point for most Laravel apps.
Wrap the builder in a document class. Don't scatter builder calls through controllers. Create App\Support\Markdown\WeeklyReport with a build(): string method. This gives you one place to change formatting and one place to test, and it insulates the rest of the app if you later swap the package out.
Use it for data-driven exports, not static docs. If the content is fixed prose, a Blade template that emits markdown is simpler and diff-friendly. Reach for the builder when the structure is dynamic: release notes from commits, per-customer summaries, API docs from a schema, exportable record dumps.
Compose per section. Build a MarkdownBuilder per logical section (summary, table, appendix), then concatenate the rendered strings with "\n\n" between them. This keeps methods short and makes sections individually testable. It also sidesteps any internal nesting quirks.
Loop Eloquent results into lists/tables:
foreach ($orders as $order) {
$md->addListingItem("{$order->id} — {$order->total}");
}
Batch queries first; the builder itself is cheap, but naive per-row relationship access inside the loop is not.
Schedule or queue it. A changelog or report generator belongs in an artisan command or a queued job, writing to Storage. Keep the HTTP path limited to triggering and downloading; don't generate large documents inline in a request.
Test the raw string, not rendered HTML. Snapshot the generated markdown (or assert on stable substrings). Rendering through CommonMark in tests adds a second source of failure and hides formatting regressions in the actual output.
Pipe to HTML only at the edge. If you need to display the result, run it through league/commonmark in the view layer. Never store both the markdown and the HTML and let them drift.
composer.json, and be prepared to vendor or fork it — the codebase is small enough that a fork is a realistic escape hatch. Budget for that risk before putting it on a critical path.*, _, #, `, |, or a leading -, you will get broken markup. Add your own escaper for table cells and inline text before passing values in.| in a value silently shifts every subsequent column. Escape to \| and strip newlines from cell content — a newline inside a row destroys the table.```` vs `). If you emit code blocks from user content, use four backticks and let the content be what it is.build(), __toString(), or an explicit render(). Calling the wrong one returns an object that silently stringifies to "" in some contexts; dump() the result during development.str_replace(' ', '·', $md)) to catch trailing-space line breaks and missing blank lines before blaming the renderer.How can I help you explore Laravel packages today?