Querying stats
The fluent builder is the recommended way to read stats. Start it with for($site) — or query() to use the configured default site — chain options, and finish with a terminal call that returns a typed result, so you stop hand-parsing JSON. Enums give type-safe metrics, periods, intervals and properties; plain strings still work everywhere.
use RoundlyConsulting\Plausible\Facades\Plausible;
Plausible::stats()->for('my-site.com'); // a fluent PendingStatsQuery for one site
Plausible::stats()->query(); // the same, for the configured PLAUSIBLE_SITERealtime visitors
$live = Plausible::stats()->for('my-site.com')->realtime(); // int — visitors on the site right nowAggregate
aggregate() returns an AggregateResult. metric() gives a metric’s value (0 when absent), change() the comparison delta when compare() was on, and has() whether the metric is in the response:
use RoundlyConsulting\Plausible\Enums\Metric;
// Aggregate metrics for a custom range, compared with the previous period
$result = Plausible::stats()
->for('my-site.com')
->metrics(Metric::Visitors, Metric::Pageviews, Metric::BounceRate)
->between(now()->subDays(7), now()) // auto-sets period=custom
->compare()
->aggregate(); // -> AggregateResult
$visitors = $result->metric(Metric::Visitors); // typed int
$change = $result->change(Metric::Visitors); // comparison delta (or null)
$result->has(Metric::BounceRate); // bool
$result->results; // ['visitors' => ['value' => 2001, 'change' => 30], …]
$raw = $result->response(); // escape hatch to the ResponseTimeseries
timeseries() returns a TimeseriesResult of TimeseriesPoint objects, each with its date as Carbon and its metrics. Plausible’s v1 timeseries endpoint takes only the date (alias day) and month intervals; minute, hour or week throw UnsupportedRequest before anything is sent — for hourly or weekly buckets use run() with Dimension::TimeHour or Dimension::TimeWeek (Stats v2 queries):
use RoundlyConsulting\Plausible\Enums\Interval;
use RoundlyConsulting\Plausible\Enums\Metric;
use RoundlyConsulting\Plausible\Enums\Period;
// Day-by-day trend you can chart
$points = Plausible::stats()
->for('my-site.com')
->metrics(Metric::Visitors, Metric::Pageviews)
->period(Period::ThirtyDays)
->interval(Interval::Date) // date or month — hourly or weekly buckets go through run()
->timeseries() // -> TimeseriesResult
->points(); // list<TimeseriesPoint>
foreach ($points as $point) {
$point->date; // Carbon
$point->metric(Metric::Visitors); // int|float
}Breakdown
breakdown() needs a property — pass it to the terminal or set it earlier with property(); without one it throws MissingConfiguration naming the missing property. Rows keep Plausible’s response keys, so read the dimension with get() (page for event:page) and metrics with metric():
use RoundlyConsulting\Plausible\Enums\Metric;
use RoundlyConsulting\Plausible\Enums\Property;
// Top pages, ranked and paginated
$rows = Plausible::stats()
->for('my-site.com')
->metrics(Metric::Visitors)
->last30Days()
->perPage(20) // default 100
->breakdown(Property::EventPage) // -> BreakdownResult
->rows(); // list<BreakdownRow>
foreach ($rows as $row) {
$row->get('page'); // "/pricing" — the dimension, by its response key
$row->metric(Metric::Visitors); // int|float
}
// Or set the property first
Plausible::stats()->for('my-site.com')->property(Property::VisitSource)->breakdown();Periods and date ranges
Skip the enum for the common ranges with named shortcuts, or pass any Period or an explicit range:
use RoundlyConsulting\Plausible\Enums\Period;
Plausible::stats()->for('my-site.com')->today()->aggregate();
Plausible::stats()->for('my-site.com')->yesterday()->aggregate(); // custom range
Plausible::stats()->for('my-site.com')->last7Days()->timeseries();
Plausible::stats()->for('my-site.com')->last30Days()->aggregate();
Plausible::stats()->for('my-site.com')->last6Months()->timeseries();
Plausible::stats()->for('my-site.com')->last12Months()->timeseries();
Plausible::stats()->for('my-site.com')->thisMonth()->aggregate();
Plausible::stats()->for('my-site.com')->lastMonth()->aggregate(); // custom range
Plausible::stats()->for('my-site.com')->thisYear()->aggregate(); // v1: custom range, 1 January to today
Plausible::stats()->for('my-site.com')->allTime()->run(); // run() only — v1 terminals throw UnsupportedRequest
// Any Period, or an explicit range
Plausible::stats()->for('my-site.com')->period(Period::SixMonths)->aggregate();
Plausible::stats()->for('my-site.com')->between(now()->subDays(14), now())->aggregate();
Plausible::stats()->for('my-site.com')->since(now()->startOfYear())->aggregate(); // year to date — through today| Shortcut | Sends |
|---|---|
today() | period=day |
yesterday() | custom range — yesterday, start to end of day |
last7Days() | period=7d |
last30Days() | period=30d |
last6Months() | period=6mo |
last12Months() | period=12mo |
thisMonth() | period=month |
lastMonth() | custom range — the previous calendar month |
thisYear() | period=year |
allTime() | period=all |
between(Carbon $start, Carbon $end) and since(Carbon $start) both set period=custom with a closed range for you: the v1 terminals send date=start,end and run() sends date_range: [start, end]. since() runs through today, in the app’s timezone, fixed when you call it. A later period() or shortcut such as last7Days() replaces an earlier between(), since(), yesterday() or lastMonth() range. The v1 terminals have no year or all-time period: thisYear() goes to them as a custom range from 1 January to today, while allTime() throws UnsupportedRequest pointing to run(), which sends both unchanged. Unset options fall back to the builder defaults: the visitors metric, a 30d period, a date interval, page 1 and 100 rows per page.
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 cryptoBy 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.