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 Extensions Deferred Laravel Package

phive/twig-extensions-deferred

A Twig extension for deferring template output. Capture blocks or expressions and render them later, making it easy to collect scripts, styles, or other content in the right place without restructuring your Twig views.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Twig extension, not a Blade feature, so the first decision is whether the app actually renders Twig. If it's a Blade app, stop here — Blade's @push/@stack already covers the same use case and this package will just add a dependency. If you're on Twig (typically via rcrowe/twigbridge in Laravel), it's a tiny, focused utility.

Version note before you install: v2.0.3 is a documentation-only release whose only content is "added a note about Twig 3 support." That is a signal, not a guarantee — a README note does not mean the code path was rewritten for Twig 3. Read the note, then confirm against the actual composer.json constraint rather than trusting either the note or older blog posts.

Minimal setup:

composer require phive/twig-extensions-deferred
composer show phive/twig-extensions-deferred   # check the twig/twig constraint yourself

Register the extension once with the Twig environment:

// Plain Twig
$twig->addExtension(new \Phive\Twig\Extensions\Deferred\DeferredExtension());
// Laravel + TwigBridge, in AppServiceProvider::boot()
Twig::addExtension(new \Phive\Twig\Extensions\Deferred\DeferredExtension());

Or via config/twigbridge.php under 'extensions' => [ ... ] so it's picked up by config caching.

Where to look first: the DeferredExtension class (to confirm the tag/attribute name in this version) and the README — including the new Twig 3 note — then the package tests if present. The legacy branch has historically diverged from the main repo, so trust the code over the prose.

First use case — a layout that owns a "scripts" slot at the bottom of the body, while each page defines its own JS without needing a matching block in the layout:

{# resources/views/layouts/app.twig #}
<!DOCTYPE html>
<html>
<head><title>{% block title %}App{% endblock %}</title></head>
<body>
    {% block content %}{% endblock %}
    <script src="/js/app.js"></script>
    {% block scripts deferred %}{% endblock %}
</body>
</html>
{# resources/views/pages/dashboard.twig #}
{% extends 'layouts/app.twig' %}

{% block content %}<h1>Dashboard</h1>{% endblock %}

{% block scripts %}
    {{ parent() }}
    <script src="/js/dashboard.js"></script>
{% endblock %}

The deferred marker tells the extension to buffer that block's output and emit it after the main rendering has completed, rather than inlining it at the point of declaration. That's what makes it useful as an asset-collection slot.

Implementation Patterns

Layout-owned deferred slots. Declare all deferred blocks in the base layout (scripts, stylesheets, modals, etc.) and let page templates fill them. Keep the declaration order stable so output order is predictable.

parent() chaining still applies. If the layout puts default scripts inside the deferred block, child templates should call {{ parent() }} to keep them. This is the same inheritance rule as normal Twig blocks — deferred only changes when the output is flushed.

Partials and includes contributing. Includes can define blocks that bubble up into a deferred slot, which is handy for component-driven pages: a partials/chart.twig include can register its own <script> without the page template knowing about it. Test this explicitly — block scoping across include/embed is where behavior gets surprising.

Registration as infrastructure, not per-request. Add the extension in a service provider (or TwigBridge config) and never inside controllers. Once registered, treat the deferred keyword as a template convention: document it in the layout's header comment so new developers know where the magic comes from.

Testing pattern. Render pages through Twig in feature tests and assert on ordering, not just presence:

$html = Twig::render('pages/dashboard.twig');
$this->assertStringContainsString('/js/dashboard.js', $html);
$this->assertLessThan(
    strpos($html, '/js/dashboard.js'),
    strpos($html, '</body>') // script appears before body close, not floating after <html>
);

That single assertion catches the most common failure mode — deferred output landing in the wrong place. It also catches regressions if a future release (including whatever follows the Twig 3 note) changes flush timing.

Extension point. If you need named flush points (e.g. {{ flush_deferred() }} mid-template), you'd subclass the extension's token parser or add your own. In practice, resist this: if you're inventing multiple flush points, your template inheritance is probably fighting you.

Gotchas and Tips

Read the Twig 3 note, then verify it. v2.0.3's sole change is a documentation note about Twig 3 support. Treat it as a prompt to check, not a compatibility claim: run composer why-not twig/twig ^3.0 and read the require block. If the constraint still caps at ^1.0|^2.0, the note is aspirational and the old advice stands — pin Twig or vendor-patch the extension yourself. If the constraint now allows ^3.0, the assessment changes materially and you should re-test the deferred flush against your Twig version before rolling out. Either way, a doc-only release means no code changed: existing behavior (bugs included) is identical to whatever you were running before.

Legacy branch, thin maintenance. This is the -legacy repo — historically low stars and near-zero dependents. Even with a Twig 3 note, budget for vendor-patching rather than expecting upstream fixes. Assume you are effectively the maintainer.

Compiled-template cache. Twig compiles templates. After adding the extension you'll get Unknown tag "deferred" or Unknown "deferred" until the cache is cleared. In Laravel that's php artisan view:clear (TwigBridge caches under storage/framework/views or the configured Twig cache path). Do this in deploy scripts whenever the extension set changes — and definitely after bumping to v2.0.3, since a version bump alone won't invalidate compiled templates in every setup.

Not the same as Blade stacks. @push('scripts') in Blade is the direct analogue, and if the team is mixed Blade/Twig, you'll have two asset-collection mechanisms. Pick one convention per app, not per template.

Whitespace and escaping. Deferred blocks buffer rendered output, including whitespace. Use {%- ... -%} control if the script tags end up with stray blank lines. Content is still escaped by Twig's autoescape — don't reach for |raw to "fix" a script tag; pass data via json_encode from PHP instead.

Ordering with multiple deferred blocks. Multiple deferred blocks flush in the order the extension tracks them, which is not always the order you visually expect in a layout with conditional blocks. If ordering matters, consolidate into one deferred slot rather than relying on declaration order.

Debugging. If content silently disappears, first verify the child block name matches the layout's deferred block name exactly, then check whether the child's block is nested inside another block (Twig block names are global; nesting a {% block %} inside another block is legal but confusing). Dump the compiled template to see what the extension generated — this is also the fastest way to confirm the extension is loaded at all after a version bump.

Don't put heavy logic in deferred blocks. They render late in the request; exceptions thrown there are harder to attribute to a template, and any service calls inside them run after the main body has already been built. Deferred blocks should be presentation-only.

Escape hatch. If you're fighting this package more than once a sprint, the honest answer is to replace it with a small view composer or a dedicated AssetBag service injected into Twig via a global — same result, no abandoned dependency, and you control the semantics. The v2.0.3 doc note doesn't change that calculus: a README line is not a maintenance commitment.

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.
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle