awcodes/overlook
Overlook is a Filament plugin that adds an app overview widget to your admin panel, listing all registered resources with configurable sort order and responsive column counts. Use include or exclude methods to control which resources appear. Supports Filament 4 and 5.
Overlook is a Filament panel plugin that renders a single dashboard widget listing every registered resource with a record count and a link. Think of it as a “jump board” for your admin: pending orders, unread messages, draft posts — one glance, one click.
1. Install with a version that matches your Filament line.
composer require awcodes/overlook
| Your Filament | Use Overlook |
|---|---|
| 3.x | 2.x (legacy) |
| 4.x | 3.x or 4.x |
| 5.x | 4.x |
Pin the major ("awcodes/overlook": "^4.0") rather than floating, because the 4.x line is the only one that covers both Filament 4 and 5 and its internals moved.
2. Add the @source directive to your theme. This is the step people skip and then file bugs about. In resources/css/filament/admin/theme.css:
@source '../../../../vendor/awcodes/overlook/resources/**/*.blade.php';
Then rebuild: npm run build. Without this the widget renders unstyled — counts stack vertically, icons blow up to full width.
3. Register the plugin and the widget in your panel provider. Both are required; the plugin holds configuration, the widget does the rendering.
use Awcodes\Overlook\OverlookPlugin;
use Awcodes\Overlook\Widgets\OverlookWidget;
public function panel(Panel $panel): Panel
{
return $panel
->plugins([
OverlookPlugin::make()->sort(1),
])
->widgets([
OverlookWidget::class,
]);
}
4. First real use case. Add includes() with the three or four resources that represent actionable work, then override getOverlookWidgetQuery() on one of them to show only what needs attention:
OverlookPlugin::make()
->includes([
\App\Filament\Resources\OrderResource::class,
\App\Filament\Resources\InvoiceResource::class,
])
->alphabetical();
class InvoiceResource extends Resource implements CustomizeOverlookWidget
{
use HandlesOverlookWidgetCustomization;
public static function getOverlookWidgetQuery(Builder $query): Builder
{
return $query->where('status', 'unpaid')->where('due_at', '<', now());
}
public static function getOverlookWidgetTitle(): string
{
return 'Overdue Invoices';
}
}
That combination — a curated list plus per-resource query scoping — is where the plugin earns its keep. A default install showing all 40 resources with raw table counts is noise.
Where to look first if something misbehaves: vendor/awcodes/overlook/src/OverlookPlugin.php (all the fluent config) and resources/views/widget.blade.php (layout and tooltip markup).
Two panels, two audiences. Because configuration lives on the plugin rather than in a config file, each panel gets its own Overlook instance. Give the customer-support panel a support-focused allowlist and the ops panel an inventory-focused one; both can register OverlookWidget::class without collision.
// SupportPanelProvider
OverlookPlugin::make()->includes([TicketResource::class, RefundResource::class])->sort(1)
// AdminPanelProvider
OverlookPlugin::make()->excludes([TicketResource::class])->sort(99)
Ordering against other widgets. sort() is just the index position within the dashboard widget list, not a sort of the resource items themselves. Use small integers to pin Overlook to the top and leave the default sort (2) alone if you have a stats/chart widget that should lead. Item ordering inside the widget is controlled separately by alphabetical() or the order of includes().
Responsive columns as a deliberate design choice. The default 1→5 column ramp is fine for a full-width dashboard, but if you drop Overlook into a narrower column (a custom page, a two-column dashboard), clamp the upper breakpoints:
->columns([
'default' => 1,
'sm' => 2,
'md' => 2,
'lg' => 3,
'xl' => 3,
'2xl' => null, // null = don't override; inherits the previous breakpoint
])
Per-resource icons to break the visual monotony. Every resource's own navigation icon is reused by default, which means a panel full of heroicon-o-rectangle-stack. Override only the handful that matter:
->icons([
'heroicon-o-banknotes' => \App\Filament\Resources\InvoiceResource::class,
'heroicon-o-truck' => \App\Filament\Resources\ShipmentResource::class,
])
The map is keyed icon-first, resource-second — the reverse of what most people guess.
Tenant-aware counts. Overlook calls the resource's getEloquentQuery(), so any global scope, tenancy scope, or policy-driven query constraint your resource already applies flows through for free. If counts are wrong per-tenant, fix it in the resource, not in Overlook.
Large tables. Each visible item is a count query. Ten resources means ten select count(*) round trips on every dashboard load — fine at a few thousand rows, painful at tens of millions. For heavy tables, scope the query to something indexable (where('status','pending')) rather than counting the whole table, and cache at the query level if the number doesn't need to be live:
public static function getOverlookWidgetQuery(Builder $query): Builder
{
return $query->where('status', 'pending');
}
Reusing the widget outside the dashboard. The widget is a standard Filament widget, so it can be registered on a custom page (protected function getHeaderWidgets(): array) or rendered in a Blade view if you need it on a bespoke page. When doing this on a custom dashboard implementation, verify Livewire registration — this was a real regression in early 1.x.
Counts as an ops signal, not a dashboard. The most effective pattern I've seen: keep abbreviateCount(true) (default) and tooltips(true) on the main dashboard, then use the customization interface to give a handful of resources PENDING-style filters. The abbreviated number with a hover tooltip is exactly right for a glanceable card; full precision lives on the resource list page.
includes() and excludes() are mutually exclusive. The README says it explicitly. If you call both, behaviour is undefined — pick one. In practice includes() scales better for dashboards, since most panels accumulate resources over time and an allowlist fails closed.[] (or null, from a computed config) to includes/excludes broke in early 2.x. It was fixed in 2.1.1. If you build the array dynamically, guard it and stay on a current patch release.@source = unstyled widget. This is the single most common install failure and presents as a layout bug, not a missing-CSS bug. Check public/css/filament/admin/theme.css contains the compiled Overlook classes before debugging anything in PHP.abbreviateCount() relies on PHP's intl extension (NumberFormatter) for the shorthand formatting. If the widget throws after enabling/disabling abbreviation, check php -m | grep intl on that environment — common on minimal Docker images.tooltips() only matters when counts are abbreviated. Turning tooltips off while abbreviation is on leaves users with 1.2K and no way to see the real number. Decide the pair together.withoutTrashed() is model-dependent. It respects soft deletes only when the model actually uses the SoftDeletes trait; otherwise it's a no-op. And if your custom getOverlookWidgetQuery() adds withTrashed(), your override wins — order of operations matters.$title on the resource is honoured as of 4.1.1, so a resource with a custom $title will label the card correctly without the interface. If you're on ≤4.1.0 and the label is wrong, upgrade before writing a getOverlookWidgetTitle() override you don't need.canAccess() / canViewAny(), don't assume the widget hides them too — verify, and lean on includes() keyed to the user's role in the panel provider if not. Leaking the existence and count of a resource you can't open is a small but real information disclosure.route:cache / config:cache / view:cache in production, run php artisan optimize:clear after changing plugin configuration, then re-warm. A widget that "won't update" is usually a cached Blade view.CustomizeOverlookWidget + HandlesOverlookWidgetCustomization. The trait exists so you can add the interface to a resource and override one method without reimplementing the rest. Don't patch the vendor view; publish if you must, but the contract handles 95% of cases.How can I help you explore Laravel packages today?