goetas/twital-bundle
A Symfony bundle that integrates Twital, an HTML-syntax extension for Twig. It lets you write templates using familiar HTML-like tags and attributes while keeping Twig’s features, and wires Twital into Symfony configuration and templating.
First, the important framing: goetas/twital-bundle is a Symfony bundle, not a Laravel package. It wires the goetas/twital templating engine (Twig syntax plus TAL-style attributes and macro/slot composition) into Symfony's templating service. There is no service provider, no Blade integration, no php artisan hook — and with 0 dependents and ~20 stars, there is no community glue to lean on. If your project is a pure Laravel app, this package is not something you composer require and start using.
What changed in 1.6.0: a single compatibility PR (hason, #15) bringing the bundle in line with Symfony 3, 4 and 5. No new features, no deprecations, no API changes. The practical signal is the supported floor and ceiling: the bundle is maintained against the Symfony 3–5 line, so treat Symfony 6/7 as unverified territory rather than the default assumption.
Where it is relevant for a Laravel developer day-to-day:
goetas/twital library directly.Getting started on the Symfony side (confirm your Symfony version first — see Gotchas):
composer require goetas/twital-bundle:^1.6
// config/bundles.php
return [
// ...
Goetas\TwitalBundle\GoetasTwitalBundle::class => ['all' => true],
];
# config/packages/twital.yaml
twital: ~
Then confirm the engine is registered: bin/console debug:container twital (the exact service id and option keys live in the bundle's DependencyInjection/Configuration.php; check that file rather than trusting blog posts).
Getting started on the Laravel-adjacent side (standalone, no bundle):
composer require goetas/twital
Bind the engine as a singleton in a service provider, point it at storage_path('framework/views/twital'), and wrap ->render('view.twital', $vars) behind a small façade. That is basically the same wiring the Symfony bundle does — you are reimplementing it, because nothing ships for Laravel.
First use case: a template where repeating/conditioning lives as attributes rather than blocks:
<ul>
<li t:repeat="item items" t:content="item.name"></li>
</ul>
Symfony: inject the engine service, don't reach into the container.
use Goetas\TwitalBundle\Twital\Engine;
final class InvoiceMailer
{
public function __construct(private Engine $twital) {}
public function render(array $data): string
{
return $this->twital->render('@App/invoice.twital', $data);
}
}
Template layout convention. Put .twital files in templates/ and resolve them by relative path from the bundle/root; Twital's own loader handles the extension. Don't mix .twig and .twital in the same folder — Twital is not Twig, and Twig filters/functions will silently not exist.
Composition with macros/slots (the main thing Twital gives you over Twig). Use metal:use-macro and t:block-style slots for layout inheritance instead of Twig's {% extends %}:
<!-- layout.twital -->
<html>
<body>
<div metal:define-macro="content"></div>
</body>
</html>
<!-- page.twital -->
<div metal:use-macro="layout.twital/content">
<h1 t:content="title"></h1>
</div>
This gives server-rendered fragment composition with clean override points — genuinely useful for email templates and PDF layouts where you'd otherwise litter @include with conditional variables.
Integration tip for Laravel-only environments. If you adopt Twital standalone, register it as a view engine rather than calling it ad-hoc:
// AppServiceProvider::boot()
$this->app->singleton('twital', fn () => new \Goetas\Twital\Twital(
storage_path('framework/views/twital'),
config('twital.options', [])
));
\View::addExtension('twital', 'twital', function ($app) {
return new TwitalViewEngine($app['twital']);
});
Then view('mail.invoice.twital', $data) works and you keep the rest of your Blade world untouched. This is the only realistic "day-to-day" story for a Laravel developer.
Cache invalidation. Both paths have an on-disk compiled cache. Wire php artisan view:clear (or your own command) to rm -rf storage/framework/views/twital, otherwise you will spend an hour chasing "why did my template change not appear."
composer require the bundle in a Laravel app. It declares a Symfony\Bundle dependency and provides no ServiceProvider; auto-discovery will do nothing and you'll get confusing container errors or nothing installed at all.composer why-not goetas/twital-bundle ^1.6 and read the composer.json requirements before wiring anything in. This is the single most important thing the new release tells you.{{ ... }}-style syntax lives inside attribute values, not at the top level. {% for %}, {% extends %}, and most Twig extensions are unavailable. Any Twig filter you rely on must be re-imported into the Twital environment, or expressed with TAL semantics.prepend/Configuration class controls cache dir, debug flag, and possibly template extension. Read vendor/goetas/twital-bundle/DependencyInjection/Configuration.php and the TwitalExtension before assuming a key exists.composer.json rather than any release-note summary.goetas/twital source (the Parser, Visitor, and attribute handlers) — that's where the real behaviour lives, and the bundle release cadence won't help you there.t:translate wrapping your Laravel translator).t:content escapes by default; use the explicit structure/unescaped variants deliberately. Mixing raw HTML into t:content is the single most common bug when porting from Blade, and it fails open (you'll get escaped tags in the page, not an error).Verdict for a Laravel developer: unchanged by 1.6.0. This was a Symfony-compatibility housekeeping release with no Laravel surface area whatsoever — unless you're forced into it by an existing Symfony 3/4/5 service, the day-to-day leverage is low. The realistic use remains goetas/twital (not the bundle) as a niche view engine for email/PDF fragment composition where TAL's slot semantics beat Blade @include gymnastics. Budget one afternoon of reading the underlying library; the bundle itself is effectively irrelevant to you.
How can I help you explore Laravel packages today?