league/html-to-markdown
Convert HTML to Markdown in PHP with league/html-to-markdown. Create an HtmlConverter, pass valid HTML to convert(), and get clean Markdown back. Useful for editing legacy HTML, storing content as HTML while authoring in Markdown, and turning HTML email into plain text.
^5.1.4 or later — see the security note below):
composer require league/html-to-markdown
dom, xml, libxml) are enabled.use League\HTMLToMarkdown\HtmlConverter;
$converter = new HtmlConverter();
$markdown = $converter->convert('<h1>Hello World</h1>');
echo $markdown; // => "# Hello World"
First use case: convert rich-text HTML content (e.g., from a CMS or user input) into clean Markdown for storage, editing, or export (e.g., to plain-text email).
Upgrade first if you convert untrusted HTML. 5.1.4 is a security release that fixes a denial-of-service issue where conversion time grew with the square of the number of sibling elements (GHSA-h3wg-gqjr-jjmw). Run composer show league/html-to-markdown and ensure it resolves to >=5.1.4; the fix produces identical Markdown, so it is a drop-in upgrade with no output changes.
HTMLPurifier and use strip_tags/remove_nodes options:
$converter = new HtmlConverter([
'strip_tags' => true,
'remove_nodes' => 'script iframe object',
]);
$html = Str::limit($request->input('body'), 100_000); // reject or truncate oversized payloads
TableConverter for table support):
$converter->getEnvironment()->addConverter(new MyCustomConverter());
$converter = new HtmlConverter([
'header_style' => 'atx',
'italic_style' => '*',
'bold_style' => '**',
'hard_break' => true, // GitHub-style line breaks
]);
$timeout (and retryUntil) rather than doing it inline in the request — a slow conversion then fails the job instead of tying up a PHP-FPM worker.preserve_comments to retain CMS-specific comments (e.g., <!-- {title} -->) for round-trip editing workflows. Note that preserved tags/comments were among the affected cases in the 5.1.4 advisory, so this is another reason to stay current.<pre>, <code>, table rows/cells and preserved HTML tags on all PHP versions, and essentially all elements on PHP 8.0 and below. Upgrade to >=5.1.4, and treat user HTML length as a hard constraint. If you cannot upgrade yet, run conversion in a queue with a timeout and cap input size.* and _ in text nodes by default, but still may mangle math or code blocks—test with edge cases.Converter\TableConverter (and custom handling for nested lists—see To-Do list in README). Tables are also the highest-risk structure for large documents, so validate row/cell counts before converting.convert() in libxml_use_internal_errors(true) + libxml_get_errors() if pre-parsing is needed.div is treated as block-level; newlines appear around div content—configure line-break behavior via hard_break and test with <div><p>...</p></div>.strip_placeholder_links to remove empty links (e.g., <a href="#">), and use_autolinks to control link syntax ([text](url) vs <url>).microtime(true) around convert() using fixtures of many sibling <li>/<tr> elements to confirm the fix is actually installed (a stale lock file is the usual culprit).How can I help you explore Laravel packages today?