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

Widget Laravel Package

orchid/widget

Widget system for the Laravel Orchid admin panel. Build reusable dashboard blocks from base widget classes, layout components, and chart helpers, then attach them to screens. Part of the Orchid Platform ecosystem for composing flexible, custom admin dashboards in Laravel.

View on GitHub
Deep Wiki
Context7

Getting Started

Confirm you are already inside an Orchid app before doing anything. orchid/widget is a split-out satellite of orchid/platform and arrives transitively with it — check vendor/orchid/ first; composer require orchid/widget is usually a version-pinning exercise, not an installation.

  1. Pin, don't float. Add an explicit constraint to composer.json for orchid/platform and pin the widget package to a commit SHA. If auto-discovery is enabled you are done; otherwise register the service provider in config/app.php.
  2. Read the contract first. Open vendor/orchid/widget/src and find the abstract Widget class. Everything below is shaped by that one file — name(), description(), render(), and whatever the parent Layout gives you. Read it before writing a single tile; the signatures drift across Orchid majors.
  3. Scaffold one widget by hand. No generator needed — it is a small PHP class:
namespace App\Orchid\Widgets;

use App\Models\Order;
use Orchid\Widget\Widget;

class OpenOrdersWidget extends Widget
{
    public function name(): ?string
    {
        return 'Open orders';
    }

    public function description(): ?string
    {
        return 'Awaiting fulfilment';
    }

    public function render()
    {
        return view('orchid.widgets.open-orders', [
            'count' => Order::whereNull('fulfilled_at')->count(),
        ]);
    }
}
  1. Drop it into a screen layout — rows, columns, or the dashboard screen:
public function layout(): iterable
{
    return [
        Layout::rows([
            new OpenOrdersWidget(),
            new RevenueWidget(),
        ]),
    ];
}
  1. First use case: one read-only KPI tile on the existing admin dashboard. Static number, no filters, no polling, no writes. Render it, confirm the styling matches the rest of the panel, then expand. That single tile validates the whole chain — view namespace, asset loading, middleware, guard — in under a day.

Implementation Patterns

Compose dashboards, don't template them. The value is that a dashboard becomes a list of widget instances. Once tiles are classes, adding a card is a one-line layout change and removing one is a delete — that is what keeps admin surfaces from rotting into bespoke Blade pages.

Push the data work out of render(). render() executes on every page load. Keep it to a cached read or a cheap aggregate:

public function render()
{
    return view('orchid.widgets.revenue', [
        'revenue' => Cache::remember(
            "widget.revenue.{$this->period}",
            now()->addMinutes(5),
            fn () => Order::paid()->forPeriod($this->period)->sum('total'),
        ),
    ]);
}

Put the query logic in a model scope or a small repository so the widget stays a presentation object and the aggregate is unit-testable in isolation.

Parameterize via the constructor. A RevenueWidget(string $period) that renders "today" on one screen and "this month" on another beats two nearly identical classes. Keep constructor args scalar and cache-key-friendly.

Prefer a shared screen-level query over per-widget queries. If four tiles read the same filtered dataset, hydrate it once in the screen's query() and have widgets read from the passed-in payload rather than each firing its own COUNT(*). Ten tiles on an unindexed table is ten sequential aggregates per refresh.

Gate visibility in the layout, not inside render(). Hiding data inside render() still ships an empty card and an unnecessary query. Filter the widget list before it reaches Layout::rows():

Layout::rows(collect([
    new RevenueWidget(),
    new FailedJobsWidget(),
])->filter(fn ($w) => Auth::user()->hasAccess($w->permission()))->all()),

Give every widget a permission() method as your team's convention — it makes authorization auditable at a glance instead of scattered through Blade.

Treat widgets as previews, not tools. A table widget shows the top N rows plus a link to the full Orchid screen. Users who want sorting, filtering, or export belong on a real screen; keep the card cheap.

Roll out behind a flag. New tiles behind a config flag or feature toggle let you ship a widget, watch query timing for a week, then enable it broadly — much cheaper than debugging production load after the fact.

Add a render smoke test per widget. One test that instantiates each widget and asserts non-empty output is your upgrade canary across Orchid majors, and it catches the class of failure (missing view, renamed method) that is otherwise invisible until a user opens the dashboard.

Gotchas and Tips

Zero stars and zero dependents are not a fitness signal. This is a monorepo split-out consumed via orchid/platform; its adoption is the platform's adoption. Judge the parent repo's release cadence, changelog, and issue response instead. Do not put this row into a build-vs-buy scorecard as independent market evidence.

Resolve the naming mismatch before you depend on it. The repo is tabuna/widget, the package is orchid/widget. Confirm they are the same codebase and that your composer.json resolves to the source you think it does. That is a supply-chain question, not a packaging nit.

render() runs on every page load, and every dashboard view is N queries. The single most common production regression with this pattern is a tile that scans a large table on each refresh. Mandate caching or a precomputed aggregate table before you ship more than a handful of tiles, and add a per-widget timeout where the widget layer exposes one.

Validate the render path under config:cache and route:cache. Service-provider-registered Blade directives survive caching; runtime-only registrations frequently do not. A widget that renders fine in php artisan serve and breaks under a cached production boot is a classic. Test the cached path in the spike, not in staging.

Never render admin widgets outside /admin. Widgets inherit an implicit expectation of the admin guard. Rendering one on a public or customer-facing route exposes data that was only protected by middleware. If you genuinely need the card elsewhere, re-audit authorization for that specific context with its own policy check — do not inherit the panel's guard.

Debug with logs, not dd(). Dumping inside render() corrupts the admin panel's JS layout and produces a broken page that tells you nothing. Use Log::debug() or temporarily swap in a plain-text tile to inspect what data arrives.

Empty states need a decision. 0 and "no data yet" are different facts. Render them differently (0 vs —) so ops does not chase a metric that simply has not been collected.

Build a base widget for your team's chrome. Title, badge, "last updated" stamp, and a link-to-detail are repeated in every card. Subclass the package's Widget once with your standard shell and inherit from that — it is the extension point that keeps twenty tiles consistent and makes a styling change a one-file edit.

Budget upgrade time. Orchid ships breaking changes across majors and this shim has no abstraction layer to absorb them. Plan three to five engineer-days per major upgrade, keep version constraints exact, and record the adoption rationale in an ADR so the next engineer inherits a decision rather than an orphan.

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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core