twig/extra-bundle
Twig Extra Bundle is a Symfony bundle that automatically enables all Twig extra extensions with no configuration. Install via Composer to add useful filters, functions, and tags to your Twig templates without manual setup.
First, the mental model: twig/extra-bundle is a Symfony bundle, not a Twig extension pack. Installing it adds zero filters on its own. It is a wiring layer that auto-registers whatever twig/*-extra packages happen to be installed. The day-to-day value comes from those sibling packages; the bundle just saves you the registration boilerplate — boilerplate a Laravel app has to write anyway, because Laravel does not discover Symfony bundles.
Where to look first
composer show twig/extra-bundle — check it pulled symfony/http-kernel (the bundle class extends Symfony\Component\HttpKernel\Bundle\Bundle).suggest block of the bundle's composer.json — that is the real feature list.MissingExtensionSuggestor knows the extra callables, so Unknown "slug" filter now tells you which twig/*-extra package to install. That error, not the README, is your day-to-day documentation.Minimal install for a Laravel-side Twig (TwigBridge / custom Twig\Environment)
# The bundle alone is useless; pick the extensions you actually want.
composer require twig/string-extra twig/intl-extra twig/markdown-extra
# symfony/string is a hard requirement of the `u` and `slug` filters
composer require symfony/string league/commonmark
First use case — the three filters that replace the most hand-rolled Blade helpers:
{{ article.title|u.slice(0, 60) }} {# twig/string-extra, fluent UnicodeString #}
{{ article.title|slug }} {# AsciiSlugger, transliterated #}
{{ article.body|markdown_to_html }} {# twig/markdown-extra, marked safe #}
If you are on a Symfony app (or a Symfony service inside a hybrid monorepo), composer require twig/extra-bundle twig/string-extra is genuinely all you do — the bundle is enabled in config/bundles.php and the filter is live. That is the five-minute path, and it is the only path where the bundle earns its name.
Pattern 1 — Symfony app / Symfony microservice in a hybrid repo.
Zero configuration. Add a twig/*-extra package, the bundle registers the extension on the next container compile. This is the intended usage and where the package is worth its dependency weight.
Pattern 2 — Laravel with TwigBridge (rcrowe/twigbridge).
Skip twig/extra-bundle entirely. Install the twig/*-extra packages and list the extension classes in config/twigbridge.php:
'extensions' => [
// instantiated through the container, no-arg constructors only
\Twig\Extra\String\StringExtension::class,
\Twig\Extra\Intl\IntlExtension::class,
],
Anything with a constructor dependency (MarkdownExtension, HtmlExtension, CacheExtension, CssInlinerExtension) needs a service provider instead.
Pattern 3 — bind them to an existing Twig\Environment yourself.
$this->app->extend('twig', function (Environment $twig) {
$twig->addExtension(new StringExtension());
$twig->addExtension(new MarkdownExtension(new DefaultMarkdown()));
$twig->addExtension(new IntlExtension());
return $twig;
});
Critical ordering rule: this must run before the first render. Twig throws Unable to register extension "…" as extensions have already been initialized once the environment has compiled or rendered anything. Bind during register(), or use a deferred factory that builds the environment, not boot() on a shared singleton.
Pattern 4 — server-side rendering Laravel doesn't do well.
Invoices, PDFs, email bodies, print templates. Twig + these extras is a decent fit here: markdown_to_html for notification bodies, IntlExtension's format_currency/format_date for localized output regardless of the app's config('app.locale'), StringExtension for truncation in list views. This is the highest-value, lowest-risk slice of the bundle for a Laravel team.
Pattern 5 — shared template package.
If a Symfony admin panel and a Laravel app both render the same partials, pin the same twig/*-extra versions in both and let the Symfony side use the bundle while the Laravel side registers manually. Identical filters, two registration paths.
Extension selection is your configuration. The bundle has no meaningful "disable this extension" switch. The switch is Composer: don't install twig/inky-extra if you don't want Inky. Treat composer.json as the config file.
composer require twig/extra-bundle alone leaves you with no |u, no |slug, no |markdown_to_html. Always pair it with at least one twig/*-extra.Illuminate\Support\ServiceProvider. The bundle's getContainerExtension() is never called, so nothing is registered and nothing errors — you just get silent no-ops until a template renders an unknown filter.|slug (AsciiSlugger) and Str::slug() differ on transliteration, allowed characters, and separator defaults; markdown_to_html and a Blade Markdown helper may produce different HTML. If a Blade view and a Twig view show "the same" content, diff the output — this bites during migrations.IntlExtension formatting and Laravel's trans()/__() are independent. Pick one source of truth for the locale (e.g. App::getLocale()) and pass it explicitly where Intl defaults don't match; otherwise Twig pages silently render in the server default locale.markdown_to_html is marked safe-HTLM. It is not sanitized. Combine with twig/html-extra's sanitize_html (which needs symfony/html-sanitizer) for any user-authored input.MarkdownExtension needs a converter, HtmlExtension needs an HtmlSanitizerInterface, CacheExtension needs a Twig\Cache\CacheInterface. In Symfony, wire them as services; in Laravel, hand-instantiate. Plan for it or you'll hit ArgumentCountError.twig/twig ^3.12. v4.0 (alpha) drops to PHP 8.2 and adds return types across extension points — if you subclass or decorate any of these extensions, that's a breaking change. Stay on ^3.x unless you're deliberately upgrading.bin/console debug:twig --filter=slug shows whether the filter exists and which extension owns it. In Laravel there's no equivalent — dump array_keys($twig->getFilters()) in a tinker session.markdown_to_html and sanitize_html run at render time on every request. Cache the rendered fragment, not just the compiled template; Twig's compile cache does nothing for runtime filters.$twig->getExtension(StringExtension::class) works and you can compose your own filters on top of UnicodeString without forking anything.How can I help you explore Laravel packages today?