Changing the same thing across every Blade file in a Laravel app is harder than it looks. A sed one-liner replaces every match, including hits inside strings, comments, and unrelated attributes. Forte, written by John Koster, parses .blade.php files into a typed syntax tree, lets you query that tree, and renders a rewritten template back out. Koster also wrote prettier-plugin-blade, and version 3 of that formatter runs on Forte.
composer require fortephp/forteForte needs PHP 8.2, the dom extension, and Laravel 10 through 13. Installation is composer require fortephp/forte, and the service provider registers itself so the Forte facade works immediately. Forte::parse() takes a string, Forte::parseFile() takes a path. Both run through a lexer and a tree builder and return a Document. An unclosed tag or an @if without @endif produces a diagnostic on the document plus a partial tree that can still be queried and rewritten. Rendering an unchanged document returns the same bytes, whitespace included.
Query methods return lazy Laravel collections: queryElements('form'), queryBlockDirectives(['if', 'unless']), and queryComponents(['x-alert', 'livewire:*']) are the main entry points, alongside direct lookups like elementById() and hasElement(). Attributes expose isDynamic() for bound values and attributeTokens('class') for splitting class lists. Forte also builds a DOMDocument from the tree and runs expressions through PHP's DOMXPath, with Blade constructs mapped into a forte namespace, so @if blocks become forte:if and echoes become forte:echo. Matches come back as Forte nodes, which can be fed straight into a rewrite.
apply(), rewrite(), and rewriteWith() each return a new Document and leave the original untouched. rewriteWith() takes a closure that receives a NodePath rather than the node itself. NodePath exposes parents, siblings, ancestors, and depth, plus methods like getAttribute(), setAttribute(), removeAttribute(), addClass(), renameTag(), replaceWith(), remove(), insertBefore(), and insertAfter(). skipChildren() and stopTraversal() end a traversal early. Edits are queued and applied in one pass. Longer transformations go into a Visitor subclass with enter() and leave() hooks, and a RewriteBuilder covers the declarative style: select nodes by XPath, queue mutations. A Builder class constructs new elements, directives, and echo nodes for insertions.
The article walks through three practical uses. Auditing: parse every file under resources/views and count queryComponents(['x-alert']) to find real component usages, where grep would also count comments, strings, and class names like x-alert-icon. Codemods: a rewriteWith() pass adds loading="lazy" to every <img> lacking the attribute across hundreds of views, with git restore as the rollback if the pass was wrong. CI checks: a Pest test fails the build when a POST form has no @csrf, expressed as one XPath query, //form[@method="POST"][not(.//forte:csrf)]. A second expression flags @foreach blocks whose first child lacks a wire:key attribute.
The author is clear about when Forte is overkill: one rename across thirty views is faster with sed or an IDE's structural search. Forte pays off when the rule depends on structure, when the diff is too large to review by hand, or when a check runs on every commit.
Two sibling packages build on Forte. Chisel is prettier-plugin-blade v3 rewritten against the new parser, reported to format complex real-world templates 140 times faster than the previous version; it needs Node 18 or later. Reload is an experimental Vite plugin that patches Blade changes into the DOM without a full page refresh, falling back to a full reload after a configurable number of incremental patches. Forte is MIT licensed, currently at v1.1.0, with source on GitHub and docs plus an interactive playground at fortephp.com.




Comments
No comments yet — be the first.
Open the discussion
No account or password needed — just enter your e-mail and we’ll send you a one-time sign-in link. First time here? You’re set up automatically.
Your rating will be applied automatically after you sign in.
Check your inbox
We’ve sent a sign-in link to …. Open it on this device — this tab will sign you in automatically.
Nothing arrived? Check your spam folder — and mark the mail as "Not spam" so it lands in your inbox next time.