Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Remex Html Laravel Package

wikimedia/remex-html

RemexHTML is a pure PHP HTML5 parser and serializer from Wikimedia. It implements the WHATWG tokenizer and tree construction algorithms, builds a lightweight document tree, and supports serialization and tree mutation, powering robust HTML processing in MediaWiki and other projects.

View on GitHub
Deep Wiki
Context7

Getting Started

wikimedia/remex-html is a spec-compliant HTML5 tokenizer + tree builder + serializer. There is no service provider, no facade, no publishable config — it's a library you instantiate directly. That's actually the point: it's a parser primitive you compose.

composer require wikimedia/remex-html

Check the tag against your PHP version before installing. The 2.x line targets PHP 7.x; the 3.x/4.x lines move with newer PHP requirements. Since this repo is a Gerrit mirror, Composer tags can lag the canonical mediawiki/libs/RemexHtml releases — pin a major and don't chase dev-master.

Where to look first: src/Tokenizer, src/TreeBuilder, src/Serializer, src/DOM. The class you'll actually touch 90% of the time is TreeBuilder, because everything (DOM output, normalization, sanitization) is a TreeHandler hanging off it. Read the test fixtures for real-world malformed-HTML inputs.

First use case — normalize and round-trip:

use RemexHtml\Tokenizer\Tokenizer;
use RemexHtml\TreeBuilder\TreeBuilder;
use RemexHtml\TreeBuilder\Dispatcher;
use RemexHtml\Serializer\Serializer;
use RemexHtml\Serializer\HtmlFormatter;
use RemexHtml\Serializer\SerializerOptions;

$serializer   = new Serializer(
    new HtmlFormatter,
    ['encodeEntities' => SerializerOptions::ENCODE_ALL]
);
$treeBuilder  = new TreeBuilder($serializer, ['ignoreErrors' => true]);
$dispatcher   = new Dispatcher($treeBuilder);
$tokenizer    = new Tokenizer($dispatcher, $html, ['ignoreErrors' => true]);

$dispatcher->startDocument($html);
$tokenizer->execute();
$dispatcher->endDocument($html);

$normalized = $serializer->getResult();

That is the entire mental model: Tokenizer → Dispatcher → TreeBuilder → a handler → output. Swap Serializer for DOMBuilder and you get a DOMDocument instead of a string.

Implementation Patterns

1. Normalize on write, parse once. The cleanest Laravel integration is a saving model observer or a FormRequest normalization step: parse raw editor HTML, store the normalized output in a body_html column. Readers never pay the parse cost, and you can re-normalize in a queued job when the parser version changes.

// app/Services/HtmlNormalizer.php
public function normalize(string $html): string
{
    // ... parse + serialize as above, wrapped in a singleton binding
}

Bind it in AppServiceProvider as a singleton so formatter/config objects are reused per request.

2. Tokenizer-only extraction for read-path work. If you only need links, plain text, or image sources, skip the tree builder entirely. Implement RemexHtml\Tokenizer\TokenHandler and track startTag/endTag yourself. This is dramatically cheaper than a full parse and avoids the memory spike on large documents. Track a script/style depth counter if you're harvesting text, because the tokenizer will happily emit their contents as characters.

3. TreeHandler as a transformation layer. The idiomatic MediaWiki approach is to wrap the serializer in a decorator implementing RemexHtml\TreeBuilder\TreeHandler (or extend RemexHtml\TreeBuilder\RelayTreeHandler). Intercept startTag, element, or characters, rewrite or drop nodes, and forward the rest:

class NofollowRelay extends RelayTreeHandler
{
    public function startTag(Element $element, $fosterParent)
    {
        if ($element->htmlName === 'a') {
            $element->attrs['rel'] = 'nofollow noopener';
        }
        parent::startTag($element, $fosterParent);
    }
}

This is how you build a sanitizer, heading-anchor injector, or image-src rewriter without ever materializing a DOM.

4. DOM route for querying. When you genuinely need XPath or traversal, use RemexHtml\DOM\DOMBuilder:

$domBuilder  = new DOMBuilder;
$treeBuilder = new TreeBuilder($domBuilder, ['ignoreErrors' => true]);
// ... same dispatcher/tokenizer dance
$doc = $domBuilder->getDocument();

Then query with DOMXPath. If you need the result back as HTML, feed the original input through the Serializer again — see the gotcha below about saveHTML().

5. Truncation the correct way. Don't substr() HTML. Parse, count text length in a TreeHandler, stop emitting after your budget, serialize. You get valid, closed markup every time.

6. Queue large jobs. Bulk imports (RSS, CMS migration, WordPress export) should go through a queued job with an explicit memory_limit. A 5 MB article is a real parse; do it off the request thread.

Gotchas and Tips

DOMDocument::saveHTML() is not HTML5. It emits HTML4-era markup, mishandles void elements, and mangles <template>. If you go the DOMBuilder route for querying, always serialize the final output through RemexHtml\Serializer\Serializer, not saveHTML().

You must call startDocument() and endDocument() on the Dispatcher, or the tree builder never flushes pending characters. Missing the trailing call is the most common "why is my last text node gone" bug.

Errors are parse errors, not exceptions. ignoreErrors => true is standard; otherwise a custom handler receives error() callbacks. Always set maxErrors and log them — silent malformed input is a security smell.

Guard against DoS. HTML parsing is quadratic-ish on pathological nesting. Set maxLength on the tokenizer, maxErrors on the tree builder, and maxDepth/maxChildren in the serializer options. Never parse unbounded user input on an unprotected endpoint.

This is not a sanitizer. RemexHtml tells you what the tree is; it does not decide what's safe. Allow-list tags and attributes yourself, validate URL schemes (javascript:, data:), and strip on* handlers, style URLs, and srcset entries. The <template> and foster-parenting rules can also move nodes in ways that break naive allow-list assumptions — test fragments explicitly.

No charset sniffing. Input must be valid UTF-8; normalize with mb_convert_encoding first or you'll get replacement characters and confusing parse errors.

Entity encoding is a serialization option, not a parse concern. SerializerOptions::ENCODE_ALL is safest for email/API output; ENCODE_UTF8 keeps output readable. Pick deliberately — the defaults are conservative and may differ from what your frontend expects.

Tree nodes are not DOM nodes. A SerializerNode or Element can't be handed to Symfony's DomCrawler or Livewire. Convert via DOMBuilder, then back via Serializer if output fidelity matters.

Ignore the repo metrics. 12 GitHub stars and 0 listed dependents are artifacts of it being a Gerrit mirror; this library powers MediaWiki's HTML pipeline. It's battle-tested — but it also means issues and PRs on GitHub are dead ends. File bugs upstream if you find them.

Extension points worth knowing: TokenHandler, TreeHandler/RelayTreeHandler, Formatter (for output shape), and subclassing Serializer to override startTag/endTag are the four seams you'll use. Everything else is internal.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core