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

Twig Express Bundle Laravel Package

gradientz/twig-express-bundle

Symfony bundle that integrates Twig Express templating into your Symfony app, offering a streamlined Twig-based rendering layer with simple configuration and service integration for building and rendering templates quickly.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a dev-only tool, so treat installation like installing a debug toolbar — pull it in as a dev dependency and keep it out of anything that serves real users.

composer require --dev gradientz/twig-express-bundle

Before you register it, spend two minutes on the thing the 3.1.0 release changes: the package is no longer frozen at 2017. A 3.x line with an active release means the compatibility story from the old write-up needs re-checking against reality.

# what does this release actually support?
cat vendor/gradientz/twig-express-bundle/composer.json
cat vendor/gradientz/twig-express-bundle/Resources/config/services.xml 2>/dev/null \
  || cat vendor/gradientz/twig-express-bundle/Resources/config/services.yaml

The require block answers the Symfony/Twig question definitively — trust it over any changelog. The services file is where the headline 3.1.0 change lives: the bundle's TwigExtension is now registered as a service automatically, so you no longer hand-wire it.

Register it in the dev environment only. On the classic kernel layout that means app/AppKernel.php, inside registerDevBundles():

if (in_array($this->getEnvironment(), ['dev', 'test'], true)) {
    $bundles[] = new Gradientz\TwigExpressBundle\TwigExpressBundle();
}

On Symfony 4+ use config/bundles.php guarded by environment — but check the constraints first; if the bundle still targets 3.x APIs it won't boot regardless of what 3.1.0 says.

Then find out what it exposed. 3.1.0 shipped documentation tweaks, so re-read the README before you go spelunking — it is no longer as thin as it was:

php bin/console debug:router | grep -i express
php bin/console debug:container | grep -i twig_express
ls vendor/gradientz/twig-express-bundle/Resources/config/
ls vendor/gradientz/twig-express-bundle/Controller/

The second command is new to this workflow: it confirms the auto-registered Twig extension is present and tells you its service id, which is what you'll reference if you need to override or decorate it. Load the route in the browser (/twig-express, or whatever debug:router reports) and you should get an index of templates discovered inside bundle Resources/views directories.

First use case: point it at the bundle holding your front-end prototype templates, open a template from the index, and confirm it renders standalone with no controller behind it. That single flow — "browse the tree, click a leaf, see the markup" — is still the whole value proposition. If it renders, you have a living styleguide; if it errors on a missing variable, you've found the first thing you need to fix (see Gotchas).

Implementation Patterns

Convention over configuration: one bundle, one browsable tree. The bundle discovers templates by scanning bundle view directories, so the cheapest integration is none at all — keep prototype templates in a dedicated bundle (e.g. PrototypeBundle/Resources/views/components/*.twig) and let the scanner find them. Don't try to make it browse your real application templates; prototype pages and production pages have different expectations about available context.

Use the bundle's Twig functions directly — they're loaded for you now. Pre-3.1 the extension had to be registered manually, so most old examples either avoid its custom functions or tell you to wire it up yourself. Since 3.1.0 the service is defined automatically, which means any function or filter it provides is available in every dev-environment template, including your prototype layout and any partial the express route renders. Delete any manual twig.extension service definition you inherited — leaving it in place gives you a duplicate registration or a container conflict. Grep for the extension's function names in Twig/ before assuming you know what's available:

grep -rn "new TwigFunction\|new TwigFilter" vendor/gradientz/twig-express-bundle/

Give every prototype a data fixture. The most common failure mode is a prototype that assumes {{ user.name }} or a form variable exists. Keep sample data next to the template and feed it in, either via the bundle's context/config mechanism or by making the template defensive:

{# components/card.twig #}
{% set title = title|default('Card title') %}
{% set body  = body|default('Lorem ipsum dolor sit amet.') %}
<div class="card">
  <h3 class="card__title">{{ title }}</h3>
  <p class="card__body">{{ body }}</p>
</div>

Defaults on every entry-point template is the lowest-friction pattern: the prototype renders from the browser with zero configuration, and real controllers can still pass real values.

Wrap prototypes in a chrome layout. Prototypes look wrong full-bleed. Add a dev-only base layout that includes your CSS/JS entry points and a visible "PROTOTYPE" banner, then have each prototype {% extends %} it. This also makes screenshot review with designers far less confusing.

{# prototypes/layout.twig #}
<!doctype html>
<html>
<head>
  <link rel="stylesheet" href="{{ asset('build/app.css') }}">
</head>
<body class="prototype">
  <div class="prototype__banner">PROTOTYPE — not production</div>
  {% block body %}{% endblock %}
</body>
</html>

Use it as the shared surface between backend and front-end. The realistic workflow is: front-end dev edits Twig, reloads the express route, sees the result without waiting for a controller, a route, a fixture, or a database. Backend dev then wires the approved template into a real controller with $this->render('@Prototype/components/card.twig', $realData). Because it's the same Twig file in both places, there is no handoff translation step — which is the genuine win over passing an HTML mockup around.

Bolt a smoke test onto it. Template syntax errors are cheap to catch and expensive to discover in a demo. Add a functional test that iterates the discovered templates and renders each one, asserting a 200:

public function testAllPrototypesRender()
{
    $client = static::createClient();
    foreach ($this->getPrototypeNames() as $name) {
        $client->request('GET', '/twig-express/render/'.$name);
        $this->assertSame(200, $client->getResponse()->getStatusCode(), $name.' failed');
    }
}

Even if the exact URL differs in your version, the shape of the test is right — this is the highest-value thing you can do with a freshly-revived bundle, because 3.1.0 being the first release in years means the next Symfony bump is unproven. The test converts "does it still work after the upgrade" from a manual audit into CI.

Lock down the route. Restrict by IP in your dev firewall config, or scope it to a host:

# config/packages/dev/twig_express.yaml
twig_express:
    # check the bundle's Configuration class for real keys
    path_prefix: /_prototypes

and pair it with an access rule so the route is not reachable from a shared staging host:

security:
    access_control:
        - { path: ^/_prototypes, roles: ROLE_ADMIN, ips: [127.0.0.1, ::1] }

Gotchas and Tips

Never enable this outside dev/test. It enumerates internal template structure and renders arbitrary templates — no authentication, no authorization, no output filtering. 3.1.0 does not change this. On a publicly reachable host it's an information-disclosure endpoint. Register it conditionally and add a firewall rule as belt-and-braces; do not rely on "nobody knows the URL".

3.1.0 is a sign of life, not a maintenance guarantee. The old "frozen at 2017, zero stars" verdict is now wrong and should be discarded — but one release does not make a project healthy. Re-baseline by reading composer.json constraints and grepping the source for APIs removed in Symfony 4/5 (KernelInterface::getRootDir(), Bundle::getPath(), ContainerAwareCommand, annotation routing) before you invest:

grep -rn "getRootDir\|ContainerAwareCommand" vendor/gradientz/twig-express-bundle/

If those are gone, the package has genuinely caught up. If they're present, budget for a fork or take the fallback below. Also pin the version in composer.json rather than floating — ^3.1 on a project with this release cadence is how you get surprised on a Friday.

The auto-registered Twig extension is dev-global, not express-scoped. Tagging it as a Twig extension puts its functions and filters in every template rendered in the dev environment, not just the ones served by the express route. Two consequences: a function name collision with your app's own extension (or a third-party bundle's) now fails at container compile or silently shadows, and anything the extension exposes becomes reachable from your app's dev pages. If you only want it inside prototypes, decorate or remove the service definition in your dev config rather than accepting the default.

If you followed the old advice and wired the extension manually, remove it. A hand-rolled twig.extension service definition alongside 3.1.0's automatic one is the most likely upgrade breakage — you'll see duplicate-registration errors or, worse, two instances with different constructor arguments. This is the first thing to check if a project that worked on 3.0 starts throwing after the bump.

The <title> fix is cosmetic but load-bearing for your workflow. Directory listings now render a correct document title, which matters more than it sounds if you keep a dozen prototype tabs open — you can tell them apart in the tab strip and in browser history. If you had a JS hack or a layout override patching the title, drop it.

Strict variables will bite you. Symfony dev defaults twig.strict_variables: true, so any prototype referencing an undefined variable throws instead of rendering blank. That's correct behaviour — it catches real bugs — so resist disabling it globally. Instead, supply defaults in templates (as above) or a per-template fixture. If you must relax it, scope it to the express route rather than to the whole dev environment.

Webpack Encore / asset manifest errors. Prototypes using {{ encore_entry_link_tags('app') }} fail if the manifest hasn't been built. Run yarn dev (not just yarn build) during prototyping, or guard the call. Same story for asset() with a missing file — silent in prod-style config, noisy in dev.

Cache invalidation on template discovery. The browse index is built by scanning directories, and that scan may be cached. Adding a new bundle or moving a template folder and not seeing it appear almost always means: php bin/console cache:clear. After a bundle upgrade this is doubly true — clear the cache before you file a bug against 3.1.0, because the container has changed shape and a stale one will produce misleading errors.

Template name resolution is version-sensitive. @Bundle/views/foo.twig vs @Bundle/foo.twig behaved differently across Twig/Symfony versions, and bundles that scan paths often emit the long form. If a template 404s from the browser but renders fine from a controller, this is the cause — check what the lister emits rather than guessing. A version bump is exactly when this can shift under you.

Extension points are the reason to use it over rolling your own. Look for a context/fixture provider interface, the controller class, and — new as of 3.1.0 — the now-public Twig extension service. Overriding the controller, decorating the template lister, or decorating the extension is how you add per-template sample data, a search filter, or a device-width preview toggle. Read DependencyInjection/ and Resources/config/ first — whatever is defined as a tagged service is your hook.

The 30-line fallback is still genuinely competitive. If the bundle fights you, the core feature is reproducible:

// DevController — registered only in config/routes/dev.yaml
public function index(): Response { /* Finder over Resources/views, render a list */ }
public function show(string $template): Response
{
    return $this->render('@Prototype/'.$template, $this->sampleContext($template));
}

That's easier to maintain than a lightly-maintained dependency, and you control the context fixtures. The 3.1.0 release lowers the odds you'll need this, but keep it in your back pocket.

Treat it as a prototyping toy and say so in the README. The bundle's own description says private front-end prototypes, not public pages. Don't let it drift into becoming a "staging preview server" for stakeholders — the moment it's shared more widely, the missing auth becomes a real problem, regardless of how actively the package is now released.

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.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator