NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages

Register your dashboard’s metrics under short keys — usually in a service provider’s boot() — and resolve them by name. A metric can be a class-string (resolved through the container), a closure returning a metric, or a ready instance:

use App\Models\Order;
use RoundlyConsulting\Metrics\Facades\Metrics;

// in a service provider boot()
Metrics::register('registered_users', RegisteredUsers::class);                 // class-string
Metrics::register('revenue', MonthlyRevenueGoal::class);
Metrics::register('users_by_plan', fn () => UsersByPlan::make());              // closure
Metrics::register('orders_today', fn () => Metrics::value()->count(Order::query())->range('TODAY'));
Metrics::register('signups_today', RegisteredUsers::make()->range('TODAY'));   // instance

Resolving

Metrics::has('revenue');          // true
Metrics::keys();                  // ['registered_users', 'revenue', ...] — without resolving
Metrics::get('revenue');          // a new MonthlyRevenueGoal, tagged with its key
Metrics::get('revenue')->key();   // 'revenue'
Metrics::all();                   // ['registered_users' => RegisteredUsers, ...] — all resolved
MethodReturnsNotes
register(string $key, string|Metric|Closure $metric)MetricsManagerChainable.
unregister(string $key)MetricsManagerChainable; a no-op for an unknown key.
get(string $key)MetricResolves a fresh copy and tags it with its key; throws UnknownMetricException if unregistered.
has(string $key)boolWhether the key is registered.
keys()list<string>Registered keys, without resolving them.
all()array<string, Metric>Resolves every registered metric.
dashboard(array $keys)DashboardBatch resolution — see Dashboards.

get() tags the metric with its key — key() returns it, and the MetricCalculated event carries it. An unregistered key throws RoundlyConsulting\Metrics\Exceptions\UnknownMetricException. Every get() returns a new metric — a registered instance is copied — so a range() or timezone() applied to what it returns never leaks into the next call (or, under Octane, the next request).

Serving metrics from a controller

// in a controller
return response()->json(
    collect(Metrics::all())->map->toArray()
);

// or a single metric by key, applying the request's period
return response()->json(
    Metrics::get($key)->range($request->input('period', 'TODAY'))->toArray()
);

Because every metric is Responsable, the controller can also return it directly — authorization stays your app’s responsibility:

public function show(string $key)
{
    return Metrics::get($key)->range(request('period', 'TODAY'));
}

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.