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

Laravel Widgetize Laravel Package

imanghafoori/laravel-widgetize

Laravel Widgetize turns Blade partials into self-contained widget classes that render and cache their own HTML. Keep controllers and templates lean, cache expensive page fragments, and compose complex pages from small, testable, reusable pieces.

View on GitHub
Deep Wiki
Context7

Getting Started

Install it like any other Laravel package:

composer require imanghafoori/laravel-widgetize

Laravel's package auto-discovery registers the service provider, so there is nothing to add to config/app.php on modern Laravel (5.5+). Version support has moved recently: v2.2.51 explicitly adds Laravel 11 installability, so if you are on Laravel 11 make sure you pull at least that tag ("imanghafoori/laravel-widgetize": "^2.2.51") rather than whatever happens to be locked in your composer.json from an older install. Anything past Laravel 11 is still unproven — pin the version and run a smoke test before rolling it into a large app.

The mental model to internalize first: a widget is a tiny controller for a fragment of a page. Instead of a controller returning a full view, each widget owns one chunk (a sidebar, a "latest posts" list, a stats block), fetches its own data, and renders its own template. The package adds the plumbing to call those widgets from Blade and to cache each one independently.

Start by looking at the vendor source rather than guessing:

ls vendor/imanghafoori/laravel-widgetize/src
# BaseWidget / Widget are the two things you actually touch

Then scaffold your first widget:

php artisan make:widget PostsWidget

(If that command is not listed in your version, run php artisan list | grep -i widget and check the README's generator section — the naming has shifted across releases.) A generated widget looks roughly like this:

namespace App\Widgets;

use Imanghafoori\Widgets\BaseWidget;

class PostsWidget extends BaseWidget
{
    // Which Blade view renders this widget
    protected $template = 'widgets.posts';

    // Minutes; 0 (the default) disables caching
    protected $cacheLifeTimeInMinutes = 30;

    public function html()
    {
        $posts = \App\Post::latest()->take(10)->get();

        return view($this->template, compact('posts'));
    }
}

Render it from any Blade file:

{!! \App\Widgets\PostsWidget::render() !!}

First use case to try: open your layouts/app.blade.php (or whatever the master layout is) and pull the sidebar, navbar notification bell, or footer out of the layout into widgets one at a time. This is the lowest-risk introduction because layout partials are exactly the pieces that benefit most from per-fragment caching, and you immediately see the win: the layout stays a dumb shell, and each fragment gets its own cache lifetime.

Implementation Patterns

Convention layout. Keep widgets in app/Widgets and their views in resources/views/widgets. Name the widget after the fragment (LatestPostsWidget, TopCategoriesWidget) rather than after the page. One widget = one view = one query set. When a widget grows past ~40 lines of html(), it usually wants to be split.

Widgets are pull-based, not slot-based. Blade components are for markup reuse with slots; widgets are for "give me this rendered chunk of the page, with its own data and its own cache." A useful rule: if the fragment needs its own database query, make it a widget; if it is just markup parametrized by data the parent already has, make it a component. On Laravel 11 the built-in anonymous-component story is stronger than it was, so be honest with yourself about which fragments actually need the caching layer.

Passing arguments. render() accepts arguments that flow into html(), so the same widget can be reused at different sizes:

{!! \App\Widgets\PostsWidget::render(['limit' => 5]) !!}
public function html($args = [])
{
    $posts = Post::latest()->take($args['limit'] ?? 10)->get();

    return view($this->template, compact('posts'));
}

Remember that arguments become part of the cache key, so every distinct argument creates a separate cache entry.

Caching is the whole point. Set $cacheLifeTimeInMinutes per widget to match how stale that fragment can afford to be. A footer link list can be 1440 minutes; a "latest comments" block might be 1. Use $cacheTags if you want to invalidate a group at once, and (in recent versions) override the extra key dependency hook so the cache busts when the underlying data changes:

protected $cacheLifeTimeInMinutes = 60;
protected $cacheTags = ['posts'];

// Folded into the cache key — busts automatically when data moves
public function extraCacheKeyDependency()
{
    return Post::max('updated_at');
}

This is the cleanest invalidation story the package offers: rather than manually flushing keys in observers, you express "this widget is only valid as long as the max updated_at hasn't moved." For user-specific fragments, either include auth()->id() in that dependency or disable caching for that widget entirely.

AJAX / partial refresh. Because the widget is just a class that returns rendered output, it also works well as a JSON or HTML endpoint for a poll/refresh button — expose a route that calls the widget and return the string. Some versions ship a JSON helper alongside render(); check BaseWidget for it before hand-rolling one.

Testing. Widgets are far easier to test than controller+view pairs, because render() returns a plain string:

$html = \App\Widgets\PostsWidget::render();

$this->assertStringContainsString('Latest posts', $html);

Disable caching in the test environment (lifetime 0) or use the array cache driver so tests don't leak state between cases. If you are upgrading to Laravel 11, this one-assertion test is also your cheapest compatibility check — put it in CI.

Composition. A page controller can stay thin:

public function index()
{
    return view('pages.home'); // the view is mostly @widget calls
}

and pages/home.blade.php becomes a readable map of the page's fragments. That readability is the strongest argument for adopting the package on a content-heavy site.

Gotchas and Tips

  • Unescaped output. {!! ... !!} is required because render() returns HTML. That's fine for trusted widget output, but never let user input flow into a widget template unescaped — you have now bypassed Blade's default protection at the call site.
  • CSRF tokens inside cached widgets. If a widget contains a form, caching freezes the token and users will get 419s. Either pull forms out of cached widgets, or fold csrf_token() / session id into the cache key dependency — but then you've effectively disabled caching, so usually the right answer is "don't cache forms."
  • Per-user content is the classic bug. A cached "hello, Alice" sidebar served to Bob is the first thing that goes wrong in production. Audit every widget with a non-zero lifetime for auth(), session(), or request() usage, and key or disable accordingly.
  • Cache tags need a taggable store. Redis and Memcached support $cacheTags; the file and database drivers do not. On file (the Laravel default) tags are ignored or throw depending on version — if tag invalidation "doesn't work," check CACHE_DRIVER first. Laravel 11 changed the default cache/session/database setup in fresh apps, so re-check what your CACHE_STORE actually resolves to after an upgrade rather than assuming it is still file.
  • Invalidation is your job. Time-based expiry is the default, and it is not enough for data that changes on write. Add extraCacheKeyDependency() (or an observer that flushes tags) for anything where a stale fragment would be embarrassing, e.g. prices or stock counts.
  • Query count multiplies. Each widget runs its own queries on a cache miss. A page with eight uncached widgets can silently become eight round trips; wrap the whole page render in DB::listen() or Laravel Debugbar and count once, then decide which widgets actually deserve a cache.
  • Cache key cardinality. Argument-bearing widgets invoked in a loop (per category, per user) generate one cache entry per argument set. That's fine for 20 categories, fatal for 200k users — the cache fills with one-hit entries and hit rate collapses.
  • Debugging. If output looks wrong, set $cacheLifeTimeInMinutes = 0 temporarily and confirm the widget renders correctly uncached; if it does, the problem is invalidation, not the widget. Dumping the computed cache key (via the widget's key method) tells you immediately whether your extraCacheKeyDependency() is actually varying.
  • Environment hygiene. Consider forcing lifetime 0 in local/testing so developers aren't chasing ghosts, and keep it on in production.
  • Static calls and IDE support. Widget::render()-style static entry points are convenient in Blade but invisible to autocompletion and static analysis. If that bothers the team, wrap the calls in a small helper or add @method docblocks.
  • Don't widgetize everything. A page with three fragments is a good fit; a page with one fragment is just a view with extra indirection. The package earns its keep when you have many independently cacheable, independently data-fetching regions.
  • Version pinning. v2.2.51 is the release that brought Laravel 11 support, so anything older will fail or misbehave on an L11 install. Pin ^2.2.51, and keep a smoke test that renders one widget so a future Laravel upgrade failure surfaces in CI rather than in production. The maintainer count is still one — do not float this dependency with * or dev-master.
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