anomaly/dashboard-module
A dashboard module for the Anomaly CMS/Streams platform for Laravel, providing a ready-made administrative dashboard interface to display widgets, panels, and key information within your application's control panel.
The Dashboard module is an addon for the Streams Platform (the framework under PyroCMS 3.x), not a standalone Laravel package. It gives the control panel an admin/dashboard landing page plus the widget container that other addons drop cards into. With 3 stars and no dependents, treat it as an internal ecosystem component: it assumes you are already running a Streams-powered app and that the platform's addon loader is doing the wiring.
Prerequisites before you composer require anything
anomaly/streams-platform (or PyroCMS 3.x), with the AddonServiceProvider discovery running.vendor/ and addons/.Minimal steps
composer require anomaly/dashboard-module
php artisan addon:install anomaly/dashboard-module
php artisan addon:publish anomaly/dashboard-module # only if you intend to override views/config
php artisan view:clear && php artisan assets:clear
Then hit /admin/dashboard while authenticated as an admin. If the nav item or the page is missing, the install step is almost always the culprit — the addon registers routes and sections at boot, but its tables and seeded widget records come from the install migrations.
Where to look first, in this order
src/DashboardModuleServiceProvider.php — this is the real API surface. Read the $routes, $sections, $buttons, $widgets, $assets, and $bindings properties; everything the module exposes is declared there.src/Widget/ — the widget contract/model/repository. This is what you'll extend day-to-day.resources/views/ — the dashboard shell and per-widget partials. Views here are Twig (via TwigBridge), so expect .twig, not .blade.php.migrations/ — tells you whether per-user layout persistence exists (a dashboard_widgets-style table) or whether the layout is static.Do not assume class names from any README; open src/ and confirm. The repository has essentially no external users, so there is no community-vetted documentation to fall back on.
First real use case: replace one of the stock cards with a card showing your data — e.g. "unread support tickets" or "revenue today". That exercises the whole loop: a widget registration in your own addon, a view, and a data provider. If that works, everything else in this module is variation on the same theme.
The dashboard is a widget host. You almost never edit this package; you register widgets into it from your project addon's service provider.
// addons/default/example/support-module/src/SupportModuleServiceProvider.php
protected $widgets = [
\Example\SupportModule\Widget\OpenTicketsWidget::class,
];
The widget class typically extends the module's widget extension/model and declares a title, a view, and a render()/getData() hook. Keep the widget class thin — resolve a repository in the method body rather than injecting via constructor, because Streams instantiates some addon classes through its own container conventions and constructor injection is inconsistently respected across platform versions.
If you're building a custom panel and want the same dashboard shell without installing this addon as your landing page, mirror what the service provider does: declare a section in your module class and point it at the dashboard route.
// src/YourModule.php
protected $sections = [
'dashboard' => [
'buttons' => [
'dashboard' => ['href' => 'admin/dashboard', 'text' => 'Dashboard'],
],
],
];
This is the highest-leverage pattern: the module's value is really the section/route/asset registration, not the specific cards it ships.
Streams resolves addon views through a publish/override chain. Publishing puts the views under resources/{addon-slug}/ in your app, where they win over the vendor copy.
php artisan addon:publish anomaly/dashboard-module
Then edit the published Twig partials (index.twig, the widget wrapper). Prefer this over editing vendor/ — Composer will silently wipe vendor edits on the next update, and this package gets infrequent releases, so a "temporary" vendor patch can survive for months and then vanish at the worst time.
If you need auth/role gating beyond the platform default, add middleware in your app rather than editing the addon's $routes:
// app/Http/Kernel.php or a route group in a service provider
Route::middleware(['web', 'auth', 'can:access-admin'])
->group(base_path('vendor/anomaly/dashboard-module/resources/routes.php'));
Cleaner alternative: leave the addon's routes alone, and gate the section/button visibility so unauthorized users never see the nav entry.
Standard shape used across the ecosystem — a widget's render method returns a view with data, and the view handles presentation:
public function render()
{
return view('example/support_module::widgets/open_tickets', [
'count' => $this->tickets->open()->count(),
]);
}
Cache anything expensive here. The dashboard has no built-in caching layer, and a dashboard page is the single most-loaded admin URL in most applications — five widgets each running three uncached queries is the classic first-month performance regression.
composer.json with an explicit constraint and commit the lockfile. There is no release cadence to rely on here.The install step is not optional and not idempotent-looking. Streams addons need addon:install to run their migrations and seed their registries. Skipping it produces a route that resolves to a blank page or a 404 with no error — the least helpful failure mode. If the dashboard renders empty, re-run install and check the addons table's installed flag before debugging anything else.
Twig, not Blade. Views are Twig. If you drop a .blade.php file into an override path, it will be silently ignored and you'll chase a "my change isn't showing up" bug. The view:clear command clears Streams' own compiled view cache too, but asset caches are separate (assets:clear).
Namespace and slug collisions. The addon slug (dashboard) is used for view namespaces, config keys, and translation keys. Naming your own addon dashboard-anything risks the same namespace prefix and produces confusing override resolution where your views win unexpectedly.
Class names in examples and blog posts are unreliable. Because the package has essentially no user base, any tutorial you find may reference pre-rename classes from an older platform version. Always confirm against src/ in the version you installed. This is the single biggest time sink with low-star Anomaly packages.
Upgrades are coupled. The dashboard's service provider implements Streams' addon contracts directly, so a platform minor bump can change method signatures the module overrides. Before any platform upgrade, run the dashboard route and every custom widget in a staging environment. There is no test suite of consequence in this repo to catch it for you.
Watch the Laravel/Streams constraint, not the package version. composer require anomaly/dashboard-module will happily resolve against an old platform if the constraint allows. Start from the platform version you actually run, then constrain the addon.
Debugging order that actually works: (1) php artisan route:list | grep dashboard — is the route registered at all? (2) Is the addon in the addons table as installed and enabled? (3) Does the service provider boot — php artisan tinker and check the container for the module class. (4) Only then look at views. Ninety percent of "the dashboard is broken" reports resolve at step 1 or 2.
Extension points, ranked by risk: registering widgets (safe), publishing and overriding views (safe, upgrade-friendly), overriding routes via your own group (safe), extending the widget model/repository (moderate — couples you to internal schema), editing vendor/ (avoid). If you find yourself needing to modify the module class itself, that's a signal your requirement belongs in a separate section of your own addon instead.
Consider whether you need this at all. If you're on a current PyroCMS release, the CMS ships its own control panel dashboard. For a plain Laravel/Streams app, a single route + Blade view in your own addon reproduces the useful part of this package in about 40 lines, without inheriting an unmaintained dependency whose only dependents are zero. Adopt it because you want the Streams-native widget contract and section registration, not because you want "a dashboard."
How can I help you explore Laravel packages today?