nucleos/twig-extensions
Nucleos Twig Extensions adds a practical set of Twig filters, functions, and tests for everyday templating. It integrates smoothly with Twig-based PHP projects, helping you keep templates concise, readable, and maintainable without custom extension boilerplate.
This package is a bag of Twig extensions (filters, functions, tests). Laravel ships Blade, not Twig, so the first decision is whether you actually run Twig anywhere. Two realistic entry points:
rcrowe/twigbridge. Then this is a drop-in.Install:
composer require nucleos/twig-extensions
Then read src/ before the README — each extension is a small class exposing getFilters(), getFunctions(), and/or getTests(). That's the fastest way to see the actual surface area and which ones pull in optional dependencies.
First use case: pick the extensions that replace code you've already written by hand — byte formatting (1.2 MB), humanized durations (2 hours ago), Markdown-to-HTML for stored content, number/ordinal formatting. Register just those two or three first; don't wire up the whole set.
With TwigBridge:
// config/twigbridge.php
'extensions' => [
\Nucleos\Twig\Extension\BytesExtension::class,
\Nucleos\Twig\Extension\DurationExtension::class,
\Nucleos\Twig\Extension\MarkdownExtension::class,
],
Then in a template: {{ filesize|format_bytes }}.
Register per-extension, not wholesale. The package is a collection, and several extensions only work if a suggested package is installed (Markdown parser, phone number lib, intl). Cherry-picking keeps unrelated features from breaking boot.
Wrap it in your own extension when you need defaults. If you always render durations in a specific format, or always set a locale, subclass or compose:
final class AppTwigExtension extends AbstractExtension
{
public function getFilters(): array
{
return [
new TwigFilter('ago', fn ($d) => $this->format($d)),
];
}
}
This also gives you a single place to swap the library out later — important for a package with 5 stars and no dependents.
Bridge high-value filters to Blade if you're not going all-in on Twig. You don't need to run two template engines to benefit; expose the same output via a Blade directive or view composer:
Blade::directive('bytes', fn ($expr) => "<?php echo app('twig.bytes')->format($expr); ?>");
Use it in feature tests by rendering real templates. With TwigBridge, Twig::render('...') works inside PHPUnit/Pest. Assert on rendered output rather than on the extension classes — that catches locale and escaping surprises.
Compose with intl. Most formatting filters defer to PHP's Intl or NumberFormatter. Set app()->setLocale() early in the request (or \Locale::setDefault() in a service provider) so amounts, dates, and ordinals come out right without passing a locale on every call.
Non-HTTP contexts are the sweet spot. Report generation, invoice PDFs, and queued email HTML benefit most — the filters cut the "format this number/date/size" boilerplate that otherwise lives in view models.
format_bytes call, write ten lines of PHP instead.render() because the underlying library is missing. Check composer.json suggest and require-dev to see what each extension quietly assumes. Wrap risky filters or don't register them.|raw in templates, and that's an XSS decision you should make consciously per call site. Prefer filtering the Markdown through an HTML sanitizer in the pipeline.intl and locale drift. Formats differ between environments if intl is present in one and absent in another. Pin the extension in composer.json and assert on locale-sensitive output in tests, not on env-dependent behavior.dump($twig->getFilters()) after boot, or Symfony's debug:twig. Don't assume a filter exists because the README lists it — the class may only register it conditionally.getName(). If you extend any of these classes, don't copy old Symfony-style naming boilerplate; it's removed or deprecated depending on your Twig version.composer update that bumps Twig can leave you with a non-booting app; commit the lock file and test the upgrade path.Carbon::setTestNow(). These extensions typically operate on raw DateTimeInterface, so Laravel's frozen clock may not apply. Pass explicit timestamps in tests rather than relying on global time freezing.app/Twig/ is often cheaper than waiting for upstream fixes.How can I help you explore Laravel packages today?