Statistics API
Burst Statistics exposes a PHP API for querying analytics data programmatically. The entry point is the Statistics class for prebuilt dashboard datasets, and the Statistics_Query builder for custom queries. Statistics_Query constructs and sanitizes SQL through a fluent interface and executes it through the shared Query_Executor.
Architecture overview
Show code
Statistics (class-statistics.php) ← extends Statistics_Data; install + object-cache task
└── Statistics_Data (class-statistics-data.php) ← prebuilt dashboard datasets
└── Goal_Statistics (class-goal-statistics.php) ← goal-specific queries
└── Geo_Statistics (class-geo-statistics.php) ← core country-level geo query layer
Statistics_Query (class-statistics-query.php) ← fluent query builder: create()/select()/date_range()/filters()/fetch()
├── Statistics_Allowlist ← strict-mode metric / filter / group_by / order_by allowlists
├── Statistics_Sanitizer ← input sanitization + custom-SQL safety validation
├── Metric handlers (Metrics\*) ← per-metric SELECT SQL, registered in Metric_Registry
├── FROM strategies (Query_Shapes\*) ← special-case FROM/JOIN shapes, registered in From_Strategy_Registry
└── Query / Query_Executor (Admin\Database\*) ← SQL assembly + caching / single-flight / timeout execution
All queries target a set of custom database tables prefixed with {$wpdb->prefix}burst_. Execution time for every query is automatically stored in burst_query_stats for slow-query analysis.
Database tables
| Table | Purpose |
|---|---|
burst_statistics | Raw page-hit records |
burst_sessions | Session records (browser, device, referrer, etc.) |
burst_browsers | Browser lookup table |
burst_browser_versions | Browser version lookup table |
burst_platforms | OS/platform lookup table |
burst_devices | Device type lookup table |
burst_referrers | Referrer lookup table |
burst_locations | Country/region/city lookup table |
burst_goals | Goal definitions |
burst_goal_statistics | Goal completion events |
burst_known_uids | Known visitor UIDs with first/last seen timestamps |
burst_query_stats | Query execution time statistics |
burst_statistics schema
Session-level attributes (browser_id, browser_version_id, platform_id, device_id, first_time_visit, bounce) live on burst_sessions. Custom SQL or filters that reference these attributes must use the sessions.* prefix, not statistics.*.
| Column | Type | Description |
|---|---|---|
ID | int AUTO_INCREMENT | Primary key |
page_url | varchar(191) | URL of the visited page |
page_id | int(11) | WordPress post/page ID |
page_type | varchar(191) | Post type (e.g. post, page). Not-found hits are stored with the value 404. |
time | int | Unix timestamp of the hit |
uid | varchar(64) | Visitor unique identifier |
time_on_page | int | Time spent on page (milliseconds) |
parameters | TEXT | Serialized URL parameters |
fragment | varchar(255) | URL fragment |
session_id | int | FK → burst_sessions.ID |
burst_sessions schema (selected columns)
| Column | Type | Description |
|---|---|---|
browser_id | int | FK → burst_browsers.ID |
browser_version_id | int | FK → burst_browser_versions.ID |
platform_id | int | FK → burst_platforms.ID |
device_id | int | FK → burst_devices.ID |
city_code | int | FK → burst_locations.city_code |
first_time_visit | tinyint | 1 if this session is the visitor's first |
bounce | tinyint | 1 if the session is a bounce (default 1) |
burst_locations schema
The location lookup resolves a session's city_code to country, region and city data. Country-only tracking stores one negative city_code placeholder row per country.
| Column | Type | Description |
|---|---|---|
city_code | int | Primary key (negative for country-only rows) |
city | varchar(255) | City name |
state_code | varchar(18) | State/region code |
state | varchar(255) | State/region name |
country_code | char(2) | ISO 3166-1 alpha-2 country code |
continent_code | char(5) | Continent code |
burst_query_stats schema
| Column | Type | Description |
|---|---|---|
ID | int AUTO_INCREMENT | Primary key |
sql_hash | varchar(64) | Deterministic hash of the query fingerprint payload |
sql_query | TEXT | Raw SQL that was executed |
avg_execution_time | float | Rolling average execution time in seconds |
max_execution_time | float | Slowest recorded execution in seconds |
min_execution_time | float | Fastest recorded execution in seconds |
execution_count | int | Total number of executions recorded |
last_updated | int | Unix timestamp of the most recent update |
date_range_days | int | Length (in days) of the date range used by the query |
The burst_query_stats table is pruned to the 100 slowest queries; queries that span more than 365 days are not recorded.
The Statistics_Query class
Statistics_Query is the query builder. Create an instance with a stable query id, chain setter methods to describe the query, then execute it with fetch(), fetch_row(), or fetch_var(). Every value is validated and sanitized before use. The query id generates a deterministic fingerprint hash that groups related queries together in burst_query_stats, regardless of the exact timestamps passed in.
Show code
use Burst\Admin\Statistics\Statistics_Query;
$qd = Statistics_Query::create( 'top_pages_by_device' )
->date_range( strtotime( '2024-01-01' ), strtotime( '2024-01-31' ) )
->select( [ 'pageviews', 'visitors' ] )
->filters( [ 'device' => 'mobile' ] )
->group_by( 'page_url' )
->order_by( 'pageviews DESC' )
->limit( 10 );
$rows = $qd->fetch( ARRAY_A );
Builder methods
| Method | Description |
|---|---|
create( string $id ) | Static. Returns a new builder. The id is used for deterministic fingerprinting in burst_query_stats. |
select( string|string[] $metrics ) | Metrics to retrieve (see Available metrics) |
select_raw( string $sql, array $params = [] ) | Append a raw SELECT expression with prepared %s/%d/%f parameters (admin-only; ignored in strict mode) |
date_range( int $start, int $end ) | Start/end of the date range (Unix timestamps) |
filters( array|string|null $filters ) | Key/value filters (see Available filters). Accepts an array, a JSON-encoded array string, or an empty value |
group_by( string|string[] $columns ) | Field(s) to group by. Accepts a token, a comma-separated string, a JSON array, or an array |
order_by( string|string[] $clause ) | Order clause(s), e.g. 'pageviews DESC' |
limit( int $n ) | Maximum rows to return (0 = unlimited) |
where( string $column, mixed $value, string $operator = '=', string $dtype = '%s' ) | Add a WHERE condition |
where_in( string $column, array $values, string $dtype = '%s' ) | Add a WHERE IN condition |
where_null( string $column ) / where_not_null( string $column ) | Add IS NULL / IS NOT NULL conditions |
where_group( array $conditions ) | Add a grouped AND/OR WHERE condition |
where_raw( string $expr, array $params = [] ) | Add a raw WHERE expression (admin-only; ignored in strict mode) |
having_raw( string $condition ) | Add a raw HAVING condition (admin-only; ignored in strict mode) |
join( string $alias, string $table, string $on, string $type = 'INNER' ) | Add a JOIN. $table is the table name without prefix, or a raw subquery |
with( string ...$keys ) | Pull in one or more named joins from the registry (e.g. 'sessions', 'goals', 'locations'), resolving their dependencies |
set_custom_where( string $sql, array $params ) | Set a raw WHERE fragment (admin-only; validated and prepared) |
set_date_modifiers( array $value ) | Set date interval settings for period grouping |
apply_args( array $args ) | Bulk-apply select, group_by, order_by, filters, and limit from an array |
fetch( string $output_type = ARRAY_A ) | Execute and return all rows |
fetch_row( string $output_type = ARRAY_A ) | Execute and return the first row, or null |
fetch_var() | Execute and return the first column of the first row (cache-bypassed) |
prepare_sql() | Build and return the prepared SQL string without executing it |
Bounce exclusion is applied automatically when filters['bounces'] = 'exclude' (or '!'-prefixed); there is no separate setter.
Strict mode
Statistics_Query automatically applies strict mode for non-administrator users. The allowlists are resolved by Statistics_Allowlist. In strict mode:
- Only a limited set of metrics may be selected (see
burst_allowed_metric_keysfilter). - Only a limited set of filter keys are accepted.
select_raw(),where_raw(),having_raw(), andset_custom_where()are silently ignored.
The default strict-mode metric allowlist is:
pageviews, visitors, sessions, bounce_rate, avg_time_on_page,
first_time_visitors, page_url, referrer, device
Filter exclusions
Prefix any filter value with ! to exclude rather than include matching rows:
Show code
$qd = Statistics_Query::create( 'pageviews_excluding_mobile' )
->date_range( $start, $end )
->select( [ 'pageviews', 'page_url' ] )
->filters( [ 'device' => '!mobile' ] ); // exclude mobile
The referrer filter does not include NULL referrer rows when applied in include mode. NULL referrers are included when the filter is applied in exclude mode (!example.com).
404 hits and the status filter
Hits recorded for missing pages are stored in burst_statistics with page_type set to 404. Every Statistics_Query excludes those rows by default, so pageview and visitor metrics never count not-found hits unless a status filter is explicitly set.
The virtual status filter overrides the default and selects rows by HTTP status:
| Value | Result |
|---|---|
200 | Real pages only (the default set) |
404 | Not-found hits only |
all | Both real pages and 404 hits |
status is a virtual filter: there is no status column, so it resolves against page_type (404 matches the stored 404 value, 200 matches every other row). It is accepted in both admin and strict mode.
Show code
$qd = Statistics_Query::create( 'top_404_pages' )
->date_range( $start, $end )
->select( [ 'page_url', 'pageviews' ] )
->filters( [ 'status' => '404' ] ) // return only not-found hits
->group_by( 'page_url' )
->order_by( 'pageviews DESC' );
Query fingerprinting
Every query executed via fetch(), fetch_row(), or fetch_var() is hashed and stored in burst_query_stats for slow-query analysis. The hash is deterministic: two queries with the same id, metrics, filters, group_by, order_by, and date range length (in days) will produce the same sql_hash, regardless of the actual timestamps.
Statistics_Query::get_id()
Returns the sanitized query id supplied to create().
$id = $qd->get_id();
// 'top_pages_by_device'
Statistics_Query::get_fingerprint_payload()
Returns the canonical array used to build the fingerprint hash. Absolute timestamps are intentionally excluded; date_range_days is used instead so that the same query family hashes to the same value across days.
Show code
$payload = $qd->get_fingerprint_payload();
// [
// 'id' => 'top_pages_by_device',
// 'select' => [ 'pageviews', 'visitors' ],
// 'filters' => [ 'device' => 'mobile' ],
// 'filter_exclusions' => [],
// 'group_by' => [ 'page_url' ],
// 'order_by' => [ 'pageviews DESC' ],
// 'limit' => 10,
// 'exclude_bounces' => false,
// 'date_range_days' => 30,
// ]
Statistics_Query::get_fingerprint_hash()
Returns the 64-bit FNV-1a hash of the JSON-encoded fingerprint payload.
$hash = $qd->get_fingerprint_hash();
// e.g. 'a1b2c3d4e5f60718'
Available metrics
These are the metric keys accepted in select():
| Key | Label | Notes |
|---|---|---|
pageviews | Pageviews | |
visitors | Visitors | Distinct UIDs |
sessions | Sessions | Distinct session IDs |
first_time_visitors | New visitors | |
avg_time_on_page | Avg. time on page | Milliseconds |
avg_session_duration | Avg. session duration | |
bounce_rate | Bounce rate | Percentage |
bounces | Bounced visitors | |
conversions | Goal completions | Requires goal_id filter |
conversion_rate | Goal conv. rate | |
page_url | Page | |
referrer | Referrer | |
host | Domain | |
device | Device | |
browser | Browser | |
platform | Platform | |
device_id | Device (ID) | Resolved from sessions.device_id |
browser_id | Browser (ID) | Resolved from sessions.browser_id |
platform_id | Platform (ID) | Resolved from sessions.platform_id |
country_code | Country | Resolved from locations.country_code |
continent | Continent | Resolved from locations.continent_code |
state | State | Resolved from locations.state |
state_code | State code | Resolved from locations.state_code |
city | City | Resolved from locations.city |
count | Count | Alias for pageviews |
period | Period | Used with group_by: 'period' |
time | Time | Raw timestamp column |
time_on_page | Time on page | |
uid | UID | |
page_id | Page ID |
Session-level metrics (browser_id, browser_version_id, platform_id, device_id, first_time_visit) resolve against sessions.* and automatically add the sessions join via their metric handlers. The non-bounce predicate used when bounces are excluded is COALESCE(sessions.bounce, 0) = 0. Each metric is generated by a handler class registered in Metric_Registry; custom keys are resolved through the burst_select_sql_for_metric filter.
Geo metrics resolve against the burst_locations table and automatically add the locations join. The free plugin ships the GeoIP Country database, so country_code and continent are populated without Pro.
Pro - CreatorAvailable in the Creator tier
state, state_code and city are only populated when the City-level GeoIP database is active.
The following metrics are available in Burst Pro:
Pro - CreatorAvailable in the Creator tier
| Key | Label |
|---|---|
source | Source (UTM) |
source_category | Source category |
medium | Medium (UTM) |
campaign | Campaign (UTM) |
term | Term (UTM) |
content | Content (UTM) |
parameter | URL Parameter |
product | Product |
sales | Sales |
revenue | Revenue |
page_value | Page value |
sales_conversion_rate | Sales conv. rate |
avg_order_value | Avg. order value |
adds_to_cart | Added to cart |
entrances | Entrances |
exit_rate | Exit rate |
time_per_session | Time per session |
source_category classifies each session's traffic into search, social, referral, aiReferral, paid, email or direct based on the referrer host, UTM campaign parameters and known paid click IDs.
Available filters
Filters narrow query results. Pass them as an associative array to filters().
| Key | Description | Values |
|---|---|---|
page_url | Filter by page URL | String, supports * wildcard suffix |
page_id | Filter by WordPress page ID | Integer |
page_type | Filter by post type | Public post type string |
status | Filter by HTTP status, resolved against page_type | 200, 404, all |
referrer | Filter by referrer URL | String |
device | Filter by device type | desktop, tablet, mobile, other |
browser | Filter by browser name | String |
platform | Filter by OS/platform | String |
device_id | Filter by device lookup ID | Integer |
browser_id | Filter by browser lookup ID | Integer |
platform_id | Filter by platform lookup ID | Integer |
country_code | Filter by country code | ISO 3166-1 alpha-2 string, validated against the country list |
continent_code | Filter by continent code | String, validated against the continent list |
state | Filter by state/region | String |
city | Filter by city | String |
goal_id | Filter by goal ID, or all to aggregate across active goals (admin only) | Integer or all |
bounces | Include/exclude bounced sessions (admin only) | include, exclude |
new_visitor | Filter first-time visitors (admin only) | include, exclude |
entry_exit_pages | Filter entry or exit pages (admin only) | entry, exit |
host | Filter by domain/host (admin only) | String |
parameter | Filter by URL parameter (admin only) | key, key=value |
The geo filters (country_code, continent_code, state, city) resolve against the burst_locations table and add the locations join automatically. country_code and continent_code are validated against the country and continent lists; invalid codes are discarded.
Without a status filter every query excludes page_type 404 rows, so 404 hits stay out of the standard datasets. See 404 hits and the status filter.
Pro - CreatorAvailable in the Creator tier
The following filters are available in Burst Pro:
| Key | Description | Values | Notes |
|---|---|---|---|
time_per_session | Filter sessions by total time spent | "MIN-MAX" or "MIN-" in seconds | E.g. "30-120" for 30–120 s; "600-" for 600 s and above. |
Show code
$qd = Statistics_Query::create( 'sessions_by_time_bucket' )
->date_range( $start, $end )
->select( [ 'pageviews', 'sessions' ] )
->filters( [ 'time_per_session' => '30-120' ] ); // sessions with 30–120 s total
Both MIN and MAX are integers representing seconds; internally they are multiplied by 1000 to compare against the millisecond time_on_page column. If MAX is omitted (open-ended range), use the "MIN-" format.
The Statistics class
Statistics extends Statistics_Data and exposes the prebuilt dashboard datasets. Obtain the singleton via:
$statistics = \Burst\burst_loader()->admin->statistics;
get_live_visitors_data()
Returns the number of visitors currently active on the site (within the last 10 minutes, excluding sessions that have been idle longer than the exit margin).
$count = $statistics->get_live_visitors_data();
// returns int
This method is also exposed to AI agents through the WordPress Abilities API as burst/live-visitors when the enable_abilities_api option is on. The ability response shape is [ 'visitors' => int ].
get_live_traffic_data()
Returns an array of the most recent page-hit objects within the last 10 minutes. Each object includes entry/exit/checkout flags.
$rows = $statistics->get_live_traffic_data();
Return shape (per object):
| Property | Type | Description |
|---|---|---|
active_time | float | time + time_on_page / 1000 |
utm_source | string | Session referrer |
page_url | string | Page URL |
time | int | Hit timestamp |
time_on_page | int | Time on page (ms) |
uid | string | Visitor UID |
page_id | int | WordPress page ID |
country_code | string | Visitor country code |
entry | bool | Whether this is the visitor's entry hit |
checkout | bool | Whether this page is the checkout page |
exit | bool | Whether the visitor appears to have exited |
The country_code column is appended to the live traffic query by core Geo_Statistics through the burst_live_traffic_args filter.
This method is also exposed to AI agents through the WordPress Abilities API as burst/live-traffic when the enable_abilities_api option is on. The ability accepts an optional limit (1–100, default 100) and returns [ 'items' => array, 'total' => int ].
get_today_data( array $args )
Returns summary statistics for today's dashboard block.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$args['date_start'] | int | Start of today (timestamp). Default 0. |
$args['date_end'] | int | End of today (timestamp). Default 0. |
Return shape:
Show code
[
'live' => [ 'value' => string ],
'today' => [ 'value' => string ],
'mostViewed' => [ 'title' => string, 'value' => string ],
'referrer' => [ 'title' => string, 'value' => string ],
'pageviews' => [ 'title' => string, 'value' => string ],
'timeOnPage' => [ 'title' => string, 'value' => string ],
]
This method is also exposed to AI agents through the WordPress Abilities API as burst/today-summary when the enable_abilities_api option is on. The ability accepts optional date_start and date_end integers and returns a flattened, integer-only payload (live, today, most_viewed, top_referrer, pageviews, avg_time_on_page).
get_insights_data( array $args )
Returns chart-ready datasets and timestamp metadata for the insights panel.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$args['date_start'] | int | Start timestamp. Default 0. |
$args['date_end'] | int | End timestamp. Default 0. |
$args['metrics'] | string[] | Metrics to chart. Default ['pageviews', 'visitors']. |
$args['filters'] | array | Filters to apply. Default []. |
$args['group_by'] | string | Grouping interval (auto, hour, day, week, month, year). Default 'auto'. |
$args['compare_mode'] | string | Comparison mode: previous_period or year_over_year. Default '' (no comparison). |
The return shape uses raw timestamps plus interval and spans_multiple_years metadata so the frontend can format dates locale-aware. The group_by argument lets callers force a specific interval instead of deriving it from the range length.
Return shape:
Show code
[
'timestamps' => int[], // UTC period-start timestamps
'interval' => string, // 'hour' | 'day' | 'week' | 'month' | 'year'
'spans_multiple_years' => bool, // true when the range crosses a calendar year
'datasets' => [
[
'data' => (int|float)[],
'backgroundColor' => string, // CSS custom property reference
'borderColor' => string, // CSS custom property reference
'label' => string,
'fill' => string,
'metric_key' => string, // the metric this dataset charts
'is_comparison' => bool, // false for current-period series
],
// one entry per metric
],
]
The interval (hour / day / week / month / year) is determined automatically from the date range when group_by is 'auto':
| Range | Interval |
|---|---|
| ≤ 2 days | Hour |
| 3–48 days | Day |
| 49–364 days | Week |
| 365–1095 days | Month |
| > 1095 days | Year |
Interval slots are advanced with real calendar steps, so month, week and year buckets align with the grouped SQL keys across leap years and DST boundaries.
Comparison datasets. When compare_mode is set and exactly one metric is selected, an extra dataset entry is appended with is_comparison set to true. It carries the comparison-period values aligned to the current-period x-axis, plus a comparison_timestamps array (the real comparison-period period-start timestamps) and a compare_mode string, so the frontend can render a dashed comparison line and show the correct dates in the tooltip.
| Mode | Comparison window |
|---|---|
previous_period | The period of equal length immediately before the current range |
year_over_year | The same calendar range shifted back one year |
The appended comparison entry has this shape:
Show code
[
'data' => (int|float)[],
'backgroundColor' => string,
'borderColor' => string,
'label' => string,
'fill' => 'false',
'metric_key' => string,
'is_comparison' => true,
'comparison_timestamps' => int[], // actual comparison-period period-start timestamps
'compare_mode' => string, // 'previous_period' | 'year_over_year'
]
No comparison entry is appended when compare_mode is empty or when more than one metric is requested.
This method is also exposed to AI agents through the WordPress Abilities API as burst/data with type: 'insights' when the enable_abilities_api option is on. The ability reformats the response into a series array (one entry per requested metric, each with id, label, and [ timestamp, value ] points).
The burst/data ability accepts an interval hint (auto, hour, day, week, month, year) for insights requests. The value is forwarded to get_insights_data() as group_by. When interval is omitted the first value of the legacy group_by input is used instead.
get_insights_date_modifiers( int $date_start, int $date_end, string $group_by = 'auto' )
Returns the date format strings and interval metadata used to build insight charts.
$modifiers = $statistics->get_insights_date_modifiers( $start, $end );
The optional $group_by parameter ('auto', 'hour', 'day', 'week', 'month', 'year') forces a specific interval. The return shape includes a spans_multiple_years boolean; date labels are formatted on the frontend.
Return shape:
| Key | Type | Description |
|---|---|---|
interval | string | hour, day, week, month, or year |
interval_in_seconds | int | Interval length in seconds |
nr_of_intervals | int | Number of data points in the range |
sql_date_format | string | MySQL DATE_FORMAT() pattern |
php_date_format | string | PHP date() pattern for array keys |
spans_multiple_years | bool | true when date_start and date_end fall in different calendar years |
Period counts for week, month and year intervals are calendar-aware: real boundaries are used so every generated interval slot matches a possible grouped SQL key, instead of dividing the raw range by a fixed number of seconds.
get_compare_data( array $args )
Returns current-period and previous-period summary metrics for comparison widgets.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$args['date_start'] | int | Start of current period (timestamp). |
$args['date_end'] | int | End of current period (timestamp). |
$args['compare_date_start'] | int|null | Start of comparison period. Defaults to the equivalent prior period. |
$args['compare_date_end'] | int|null | End of comparison period. |
$args['filters'] | array | Filters to apply to both periods. |
Return shape:
Show code
[
'current' => [
'pageviews' => int,
'sessions' => int,
'visitors' => int,
'first_time_visitors' => int,
'avg_time_on_page' => int,
'bounced_sessions' => int,
'bounce_rate' => float,
],
'previous' => [
'pageviews' => int,
'sessions' => int,
'visitors' => int,
'bounced_sessions' => int,
'bounce_rate' => float,
],
]
get_compare_goals_data( array $args )
Pro - CreatorAvailable in the Creator tier
Returns current-period and previous-period goal/conversion metrics.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$args['date_start'] | int | Start timestamp. |
$args['date_end'] | int | End timestamp. |
$args['filters'] | array | Filters, may include goal_id. |
$args['compare_date_start'] | int|null | Start of comparison period. |
$args['compare_date_end'] | int|null | End of comparison period. |
Return shape:
Show code
[
'view' => 'goals',
'current' => [
'pageviews' => int,
'visitors' => int,
'sessions' => int,
'first_time_visitors' => int,
'conversions' => int,
'conversion_rate' => float,
],
'previous' => [
'pageviews' => int,
'visitors' => int,
'sessions' => int,
'conversions' => int,
'conversion_rate' => float,
],
]
get_data( array $select, int $start, int $end, array $filters )
Low-level single-row query. Returns one associative array of metric values for the given period.
Show code
$row = $statistics->get_data(
[ 'pageviews', 'visitors', 'sessions' ],
strtotime( '2024-01-01' ),
strtotime( '2024-01-31' ),
[ 'device' => 'mobile' ]
);
// [ 'pageviews' => 1200, 'visitors' => 340, 'sessions' => 400 ]
get_devices_title_and_value_data( array $args )
Returns pageview counts grouped by device type.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$args['date_start'] | int | Start timestamp. Default 0. |
$args['date_end'] | int | End timestamp. Default 0. |
$args['filters'] | array | Filters to apply. Default []. |
Return shape:
Show code
[
'all' => [ 'count' => int ],
'desktop' => [ 'count' => int ],
'tablet' => [ 'count' => int ],
'mobile' => [ 'count' => int ],
'other' => [ 'count' => int ],
]
get_devices_subtitle_data( array $args )
Returns the most common browser and OS for each device type.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$args['date_start'] | int | Start timestamp. Default 0. |
$args['date_end'] | int | End timestamp. Default 0. |
$args['filters'] | array | Filters to apply. Default []. |
Return shape:
Show code
[
'desktop' => [ 'os' => string, 'browser' => string, 'device_id' => int ],
'tablet' => [ 'os' => string, 'browser' => string, 'device_id' => int ],
'mobile' => [ 'os' => string, 'browser' => string, 'device_id' => int ],
'other' => [ 'os' => string, 'browser' => string, 'device_id' => int ],
]
get_datatables_data( array $args )
Returns data for the sortable data table components in the dashboard. Colors emitted by this method are CSS custom-property strings (e.g. var(--color-blue-400)); the frontend's METRIC_COLORS map takes precedence when rendering.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$args['date_start'] | int | Start timestamp. Default 0. |
$args['date_end'] | int | End timestamp. Default 0. |
$args['metrics'] | string[] | Metrics to include as columns. Default ['pageviews']. |
$args['filters'] | array | Filters to apply. Default []. |
$args['group_by'] | string | Field to group rows by. Default []. |
$args['limit'] | int | Maximum number of rows. Default 0 (unlimited). |
$args['id'] | string | Datatable identifier (e.g. statistics_pages). Passed through to the burst_datatable_pre_data filter so integrations can short-circuit per-datatable, and used as the Statistics_Query id and by the REST layer to enforce a per-datatable metric allow-list. |
Return shape:
Show code
[
'columns' => [
[ 'name' => string, 'id' => string, 'sortable' => 'true', 'right' => 'true' ],
// …
],
'data' => array[], // raw rows from the database
'metrics' => string[],
]
Integrations can short-circuit the default query by returning a prebuilt data array via the burst_datatable_pre_data filter. See burst_datatable_pre_data.
After the response is assembled (and after burst_datatable_data has run) it is passed through the burst_datatable_response filter, which receives the full { columns, data, metrics } array plus the original $args. Integrations use this to attach extra top-level keys to a specific datatable response.
The generic data/datatable and data/ecommerce/datatable REST endpoints return 403 Forbidden. Callers must use the granular per-datatable endpoints data/datatable/{id} and data/ecommerce/datatable/{id} (e.g. data/datatable/statistics_pages). The endpoint segment {id} is set as $args['id'] and used to look up the allowed metrics from App::get_datatable_metric_allow_list(); requested metrics are intersected against that allow-list before the query runs. When no metrics are supplied the full allow-list for that datatable is used. The endpoint also enforces the datatable's required capability (from App::get_datatable_capability_requirements()) and returns 403 Access denied. when the user lacks it. Unknown datatable IDs return 404.
When a metric key has no registered label, the column name falls back to a humanized form of the key (e.g. my_custom_metric → My Custom Metric) via ucwords( str_replace( '_', ' ', $metric ) ). Custom metrics added via burst_select_sql_for_metric therefore render with a sensible default header even when burst_allowed_metrics_labels is not used.
This method is also exposed to AI agents through the WordPress Abilities API as burst/data with type: 'datatable' when the enable_abilities_api option is on. The ability reformats the response into dimensions, metrics, rows, and row_count keys, and normalizes a few group_by aliases (e.g. utm_source → source).
The burst/data ability requires a datatable_id input when type is datatable. Accepted values are statistics_pages, statistics_parameters, statistics_referrers, sources_countries, sources_campaigns, sales_products, subscription_products, sources_referrers, outgoing-links, search-terms, and forms. Requested metrics are intersected against the allow-list for that datatable; an unknown datatable_id returns burst_abilities_unknown_datatable. The pro burst/sales and burst/subscriptions-data abilities also accept the same allow-list-aware metrics (with a fallback when none of the requested metrics are valid) plus optional filters and a limit capped to 500.
get_dummy_datatable_data()
Returns a fixed-size array of synthetic datatable rows for preview, onboarding, and demo contexts. Each row is a fully populated record with realistic-looking values for every metric in the statistics_pages allow-list, including ecommerce metrics shaped as { currency, value }.
This method is wired up via the burst_datatable_pre_data filter on the App class: when $args['id'] is 'dummy_data', the filter returns the dummy rows and short-circuits the database query.
Show code
$rows = $statistics->get_dummy_datatable_data();
// [
// [
// 'page_url' => '/about-us',
// 'pageviews' => 2317,
// 'visitors' => 1689,
// 'sessions' => 1812,
// 'bounce_rate' => 42.3,
// 'avg_time_on_page' => 274,
// 'entrances' => 968,
// 'exit_rate' => 31.7,
// 'conversions' => 142,
// 'conversion_rate' => 6.1,
// 'sales' => 47,
// 'revenue' => [ 'currency' => 'USD', 'value' => 4823 ],
// 'sales_conversion_rate' => 2.0,
// 'page_value' => [ 'currency' => 'USD', 'value' => 2.08 ],
// ],
// // …15 rows total
// ]
Values are generated with wp_rand() and are not stable between calls; do not use them as fixtures in tests that assert exact numbers.
App::get_datatable_config()
Single source of truth for datatable access control. Returns a map of datatable id → configuration, where each entry has a metrics allow-list and the capability a user must hold to query that datatable. Both get_datatable_metric_allow_list() and get_datatable_capability_requirements() derive from this method, and the result is filterable via burst_datatable_config.
Show code
$config = \Burst\burst_loader()->admin->app->get_datatable_config();
// [
// 'statistics_pages' => [
// 'metrics' => [ 'page_url', 'pageviews', 'visitors', ... ],
// 'capability' => 'view_burst_statistics',
// ],
// 'sources_countries' => [
// 'metrics' => [ 'country_code', 'visitors', 'bounce_rate' ],
// 'capability' => 'view_burst_statistics',
// ],
// 'outgoing-links' => [
// 'metrics' => [ 'url', 'clicks', 'previous_clicks', 'previous_clicks_yoy' ],
// 'capability' => 'view_burst_statistics',
// ],
// 'not-found-pages' => [
// 'metrics' => [ 'page_url', 'hits' ],
// 'capability' => 'view_burst_statistics',
// ],
// // The free Search, Reading Engagement and Search Console features
// // register search-terms, reading-engagement and the read-only
// // search_console datatable (view_burst_statistics).
// // Core registers sources_countries with country-level metrics
// // (view_burst_statistics); Pro extends it with region/city and ecommerce
// // metrics and adds sources_campaigns (view_burst_statistics), sales_products
// // and subscription_products (view_sales_burst_statistics), sources_referrers
// // and forms (view_burst_statistics).
// ]
The granular datatable endpoints enforce the per-datatable capability before running the query; a caller without it receives 403 Access denied.. Ecommerce datatables (sales_products, subscription_products) require view_sales_burst_statistics.
App::get_datatable_capability_requirements()
Returns a map of datatable id → required capability, derived from App::get_datatable_config().
Show code
$caps = \Burst\burst_loader()->admin->app->get_datatable_capability_requirements();
// [
// 'statistics_pages' => 'view_burst_statistics',
// 'sources_countries' => 'view_burst_statistics',
// 'sales_products' => 'view_sales_burst_statistics',
// // …
// ]
App::get_datatable_metric_allow_list()
Returns the per-datatable metric allow-list used by both the REST routes and the abilities API to gate which metrics each granular datatable endpoint can expose. The list is keyed by datatable id and is derived from App::get_datatable_config().
Show code
$allow_list = \Burst\burst_loader()->admin->app->get_datatable_metric_allow_list();
// [
// 'statistics_pages' => [ 'page_url', 'pageviews', 'visitors', ... ],
// 'statistics_parameters' => [ 'parameter', 'parameters', 'visitors', ... ],
// 'statistics_referrers' => [ 'referrer', 'source_category', 'visitors', 'sessions', ... ],
// 'sources_countries' => [ 'country_code', 'visitors', 'bounce_rate' ],
// 'dummy_data' => [ 'page_url', 'pageviews', 'visitors', ... ],
// 'outgoing-links' => [ 'url', 'clicks', 'previous_clicks', 'previous_clicks_yoy' ],
// 'not-found-pages' => [ 'page_url', 'hits' ],
// 'search_console' => [ 'query', 'clicks', 'impressions', 'click_through_rate', 'position' ],
// // Pro extends sources_countries and adds: sources_campaigns, sales_products, subscription_products, sources_referrers, forms
// ]
Register metrics for a custom datatable id through burst_datatable_config. Without an entry the REST handler responds with 404 Unknown datatable endpoint. and the abilities API returns burst_abilities_unknown_datatable.
The search_console datatable
When the Google Search Console integration is connected, Burst exposes the stored search-query rows as the read-only search_console datatable. The rows are supplied through the burst_datatable_pre_data filter for the search_console id, read from the burst_search_terms table for the matched property over the requested date range, so the default SQL query never runs. The datatable requires the view_burst_statistics capability.
| Metric | Description |
|---|---|
query | Search query |
clicks | Clicks in the date range |
impressions | Impressions in the date range |
click_through_rate | Click-through rate as a percentage (0–100) |
position | Impression-weighted average position |
Clicks and impressions are summed across days; click_through_rate is recomputed from the summed totals and position is the impression-weighted average, matching how Search Console aggregates rather than averaging daily ratios. When no property has been matched for the site, the datatable returns an empty data array.
The not-found-pages datatable
Burst records hits to missing pages with page_type 404 and exposes the top not-found URLs as the not-found-pages datatable. The rows are supplied through the burst_datatable_pre_data filter for the not-found-pages id, so the default SQL query never runs. The datatable requires the view_burst_statistics capability and is mapped to the engagement tab.
| Metric | Description |
|---|---|
page_url | URL of the missing page |
hits | Number of 404 hits in the date range |
Rows are ordered by hits descending. The same aggregation is available outside the datatable through the not_found_pages data type on the burst_get_data filter, which returns an unlimited array of { page_url, hits } rows for the requested date range:
Show code
$rows = apply_filters(
'burst_get_data',
[],
'not_found_pages',
[
'date_start' => strtotime( '2024-01-01' ),
'date_end' => strtotime( '2024-01-31' ),
]
);
// [
// [ 'page_url' => '/old-post', 'hits' => 128 ],
// [ 'page_url' => '/typo-link', 'hits' => 44 ],
// // …
// ]
Both paths accept the standard filters argument and force the status filter to 404 internally, so they always return not-found hits regardless of the caller's filters.
Executing queries
Queries are executed by calling the fetch methods directly on the Statistics_Query object. Each method assembles the SQL, applies the per-query MAX_EXECUTION_TIME optimizer hint, and runs it through the shared Query_Executor, which handles object-cache result caching, single-flight coordination, timeout cooldown, and slow-query telemetry.
Statistics_Query::fetch( string $output_type = ARRAY_A )
Executes the query and returns all matching rows.
Show code
$rows = Statistics_Query::create( 'monthly_pageviews' )
->date_range( $start, $end )
->select( [ 'page_url', 'pageviews' ] )
->group_by( 'page_url' )
->fetch( ARRAY_A );
$output_type accepts OBJECT, ARRAY_A, or ARRAY_N. Results are cached in the WordPress object cache for the duration returned by burst_query_results_cache_ttl (default 30 s; 300 s for date ranges longer than 30 days). When a persistent object cache is active, concurrent requests for the same cache key are deduplicated via a single-flight lock — only the first request runs the heavy query, followers either pick up the cached result or return [] immediately to prevent thundering-herd fan-out. When the query times out, fetch() logs the timeout, writes a short-lived cooldown marker for the cache key, and returns [] rather than surfacing the error.
Statistics_Query::fetch_row( string $output_type = ARRAY_A )
Executes the query and returns the first matching row, or null.
Show code
$row = Statistics_Query::create( 'monthly_totals' )
->date_range( $start, $end )
->select( [ 'pageviews', 'visitors' ] )
->fetch_row( 'OBJECT' );
$output_type accepts OBJECT, ARRAY_A, or ARRAY_N. Results are cached, single-flighted, and timeout-protected with the same semantics as fetch(). If the query times out, fetch_row() logs the timeout, writes a cooldown marker, and returns null.
Statistics_Query::fetch_var()
Executes the query and returns a single scalar value (the first column of the first row). This call is cache-bypassed — result caching and single-flight are disabled — so it always hits the database. When MySQL aborts the query because the timeout was exceeded, fetch_var() logs the timeout and returns null.
Show code
$val = Statistics_Query::create( 'monthly_pageviews' )
->date_range( $start, $end )
->select( [ 'pageviews' ] )
->fetch_var();
Statistics_Query::prepare_sql()
Builds and returns the prepared SQL string from the query object without executing it. Useful for debugging.
Show code
$sql = Statistics_Query::create( 'debug_query' )
->date_range( $start, $end )
->select( [ 'pageviews' ] )
->prepare_sql();
error_log( $sql );
The returned SQL is built for direct use with $wpdb and already includes the MAX_EXECUTION_TIME optimizer hint applied at execution time. Do not attempt to re-prepare it.
Query timeouts
Every executed query is prefixed with a MAX_EXECUTION_TIME optimizer hint. Background cron requests use a longer ceiling than foreground dashboard requests so that aggregation and backfill jobs are not killed mid-run.
| Context | Default | Filter |
|---|---|---|
| Foreground (REST, admin) | 30 000 ms (30 s) | burst_query_timeout_ms |
Background (wp_doing_cron()) | 900 000 ms (15 min) | burst_query_timeout_ms_background |
The foreground default can also be overridden via the burst_query_timeout_ms option (positive integer, in milliseconds). The filter always runs last and wins.
should_recommend_object_cache()
Static helper that returns true when Burst has recorded a slow analytics query and no persistent object cache is active. Used to drive the "persistent object cache recommended" admin task; safe to call from any context.
if ( \Burst\Admin\Statistics\Statistics::should_recommend_object_cache() ) {
// Surface a hint to enable Redis or Memcached.
}
Decision logic:
- Returns
falseimmediately whenwp_using_ext_object_cache()istrue. - Returns
falsewhen theburst_query_statstable does not yet exist. - Otherwise reads
MAX(max_execution_time)fromburst_query_statsand returnstruewhen it meets or exceeds the threshold returned by theburst_object_cache_recommendation_threshold_secondsfilter (default 10 seconds; clamped to a 0.1 s floor).
format_number( int $number, int $precision = 2 )
Formats a number with locale-aware separators and shorthand suffixes for large values (k, M, G, …).
echo $statistics->format_number( 123456 ); // '123k'
echo $statistics->format_number( 1234 ); // '1,234'
format_uplift( float $original_value, float $new_value )
Returns a formatted uplift string such as +12% or -5%, or an empty string when there is no change.
calculate_uplift( float $original_value, float $new_value )
Returns the percentage change as an integer.
calculate_uplift_status( float $original_value, float $new_value )
Returns 'positive', 'negative', or ''.
get_lookup_table_name_by_id( string $item, int $id )
Resolves a lookup-table ID to a human-readable name. Results are object-cached.
$name = $statistics->get_lookup_table_name_by_id( 'device', 2 );
// 'mobile'
$item must be one of: browser, browser_version, platform, device.
Goal statistics
The Goal_Statistics class handles goal-specific queries.
$goal_stats = \Burst\burst_loader()->admin->goal_statistics;
get_goals_data( array $args )
Returns the full data payload for a goal detail card.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$args['goal_id'] | int|string | Goal ID to query, or 'all' to aggregate across every active goal. Default 0. |
$args['date_start'] | int | Start timestamp. Default 0. |
$args['date_end'] | int | End timestamp. Default 0. |
When goal_id is 'all', the counts aggregate distinct converting visitors across all goals with status active, the date range starts at the earliest active goal's creation date, and the returned goalId is the string 'all'.
Return shape (abbreviated):
Show code
[
'today' => [ 'value' => int, 'tooltip' => string ],
'total' => [ 'value' => int, 'tooltip' => string ],
'topPerformer' => [ 'title' => string, 'value' => int ],
'conversionMetric' => [ 'title' => string, 'value' => int, 'tooltip' => string, 'icon' => string ],
'conversionPercentage'=> [ 'title' => string, 'value' => int, 'tooltip' => string ],
'bestDevice' => [ 'title' => string, 'value' => int, 'icon' => mixed ],
'dateCreated' => int,
'dateStart' => int,
'dateEnd' => int,
'status' => string,
'goalId' => int|string,
]
get_live_goals_count( array $args )
Returns the number of goal completions for a given goal since midnight today.
$count = $goal_stats->get_live_goals_count( [ 'goal_id' => 3 ] );
Pass 'all' as the goal_id to count distinct converting visitors across every active goal since midnight today; the method returns 0 when there are no active goals.
$count = $goal_stats->get_live_goals_count( [ 'goal_id' => 'all' ] );
Pro statistics queries
Pro - CreatorAvailable in the Creator tier
The following queries are available in Burst Pro. They back the data/page-parameters and data/page-parameter-counts endpoint types and the outgoing-links datatable.
get_page_parameters_data( array $args )
Returns the parameter=value variations recorded for a single page URL within a date range, with pageview and visitor counts. Backs the progressively loaded parameter-variations accordion under each pages row.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$args['date_start'] | int | Start timestamp. Default 0. |
$args['date_end'] | int | End timestamp. Default 0. |
$args['page_url'] | string | Page URL to query. Required. |
Returns a datatable-shaped array with columns (parameter, pageviews, visitors) and data. When any required input (date_start, date_end or page_url) is missing it returns the columns with an empty data array. Results are capped at 200 rows.
get_page_parameter_counts( array $args )
Returns a flat map of [ page_url => parameter_variation_count ] for the date range. Used to render the "n variations" badge and the expandable-row indicator on the pages datatable without inflating that query.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$args['date_start'] | int | Start timestamp. Default 0. |
$args['date_end'] | int | End timestamp. Default 0. |
Returns an associative array keyed by page URL; an empty array when the date range is missing.
Outgoing links datatable
When the track_external_links option is enabled, the outgoing-links datatable exposes click counts for tracked external links across three windows. Rows are supplied through the burst_datatable_pre_data filter for the outgoing-links id, and the response is augmented with a scraping_progress integer (0–100) via burst_datatable_response while Burst scrapes the site for external links.
| Metric | Description |
|---|---|
url | External link URL |
clicks | Clicks in the current period |
previous_clicks | Clicks in the previous equal-length period |
previous_clicks_yoy | Clicks in the same window one year earlier |
Hooks reference
burst_on_page_offset
Adjusts the number of seconds added to a visitor's last-seen time before they are considered to have left a page. Used in live traffic and live visitor calculations.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$offset | int | Offset in seconds. Default 60. |
Example:
add_filter( 'burst_on_page_offset', function( $offset ) {
return 90; // consider visitors active for 90 s after their last hit
} );
burst_live_traffic_args
Filters the Statistics_Query object used to fetch the raw live traffic rows before it is executed. Core Geo_Statistics uses this hook to append the visitor country_code to the live traffic query.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$qd | Statistics_Query | Query object for the live traffic query. |
Example:
Show code
add_filter( 'burst_live_traffic_args', function( $qd ) {
$qd->limit( 50 ); // cap live traffic at 50 rows
return $qd;
} );
burst_statistics_query
Action that fires just before a Statistics_Query is executed (fetch(), fetch_row(), fetch_var(), or prepare_sql()). Use it to mutate the query object — add named joins, custom WHERE conditions, or extra SELECT expressions — based on the selected metrics or filters. This replaces the removed burst_build_where_clause filter.
Callbacks should append to any existing custom WHERE (read it with get_custom_where()) rather than overwrite it, so multiple handlers — for example core geo clauses and Pro campaign/ecommerce clauses — compose regardless of registration order.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$qd | Statistics_Query | The query object that is about to run. |
Example:
Show code
add_action( 'burst_statistics_query', function( $qd ) {
// Admin context only: custom WHERE is ignored in strict mode.
if ( in_array( 'country_code', $qd->get_select(), true ) ) {
$where = $qd->get_custom_where() . ' AND locations.country_code IS NOT NULL';
$qd->set_custom_where( $where, [] );
}
} );
burst_datatable_data
Filters the raw data rows returned for a datatable before they are sent to the client.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$data | array | Array of result rows (associative). |
$qd | Statistics_Query | The query object that produced the data. |
Example:
Show code
add_filter( 'burst_datatable_data', function( $data, $qd ) {
foreach ( $data as &$row ) {
$row['pageviews'] = max( 0, (int) $row['pageviews'] );
}
return $data;
}, 10, 2 );
burst_datatable_response
Filters the fully assembled datatable response ({ columns, data, metrics }) returned by get_datatables_data(), after burst_datatable_data has run. Use it to attach extra top-level keys to a specific datatable response (for example a progress indicator).
Parameters:
| Parameter | Type | Description |
|---|---|---|
$response | array | The assembled response with columns, data and metrics keys. |
$args | array | The original $args passed to get_datatables_data(), including id. |
Example:
Show code
add_filter( 'burst_datatable_response', function( $response, $args ) {
if ( ( $args['id'] ?? '' ) !== 'my_custom_datatable' ) {
return $response;
}
$response['my_meta'] = 'value';
return $response;
}, 10, 2 );
burst_datatable_pre_data
Short-circuits get_datatables_data() so integrations can supply prebuilt rows without running the default SQL query. Return null to let the default query run, or an array of rows to skip it. When an array is returned, the outer method still applies the configured metric columns and labels.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$data | array|null | Prebuilt rows, or null to fall through to the default query. |
$args | array | The original $args array passed to get_datatables_data(), including the id key when the call came from a granular datatable endpoint. |
Example:
Show code
add_filter( 'burst_datatable_pre_data', function( $data, $args ) {
if ( ( $args['id'] ?? '' ) !== 'my_custom_datatable' ) {
return $data;
}
return my_plugin_build_rows( $args );
}, 10, 2 );
burst_datatable_config
Filters the per-datatable configuration returned by App::get_datatable_config(). Each entry pairs a metric allow-list with the capability required to query that datatable. Use this to register a custom datatable id, its allowed metrics, and its capability gate. Without an entry for an id, REST requests to data/datatable/{id} (or data/ecommerce/datatable/{id}) return 404, and the burst/data ability rejects the id as unknown.
Existing entries can also be extended: Pro merges extra metrics into the core sources_countries config (region, city and ecommerce metrics) through this filter rather than redefining the entry. The core Search Console feature uses it to register the read-only search_console datatable, and the core Errors feature registers the not-found-pages datatable the same way.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$config | array<string, array{metrics: string[], capability: string}> | Map of datatable id → { metrics, capability }. |
Example:
Show code
add_filter( 'burst_datatable_config', function( $config ) {
$config['my_plugin_orders'] = [
'metrics' => [ 'product', 'sales', 'revenue', 'avg_order_value' ],
'capability' => 'view_sales_burst_statistics',
];
return $config;
} );
Pair this with a burst_datatable_pre_data callback (matched on $args['id']) if the rows do not come from the default Burst tables.
@deprecated Use burst_datatable_config instead of burst_datatable_metric_allow_list. Metric allow-lists are now derived from the config returned by burst_datatable_config, which also carries the per-datatable capability requirement.
burst_select_sql_for_metric
Provides SQL for custom metric keys that are not built into Metric_Registry. The filter is chain-safe: the first argument is the SQL accumulated by earlier handlers (initially an empty string), so multiple registrants (core geo, Pro campaigns/ecommerce, your own) compose without clobbering each other. Return the incoming $sql unchanged when your callback does not own the metric. The query object is passed as a third argument so callbacks can pull in required joins via $query_data->with().
Parameters:
| Parameter | Type | Description |
|---|---|---|
$sql | string | SQL accumulated by earlier handlers (empty string when none has resolved the metric). |
$metric | string | The metric key that was not matched internally. |
$query_data | Statistics_Query | The current query object. |
Example:
Show code
add_filter( 'burst_select_sql_for_metric', function( $sql, $metric, $query_data ) {
if ( $sql !== '' ) {
return $sql; // already resolved by another handler
}
if ( $metric === 'my_metric' ) {
return 'COUNT(DISTINCT statistics.my_column)';
}
return $sql;
}, 10, 3 );
burst_default_metric
Filters the fallback metric used when an invalid or unknown metric key is supplied to select().
Parameters:
| Parameter | Type | Description |
|---|---|---|
$metric | string | The fallback metric key. Default 'pageviews'. |
Example:
add_filter( 'burst_default_metric', function( $metric ) {
return 'visitors';
} );
burst_allowed_metric_keys
Filters the list of metric keys permitted in strict (non-admin) mode.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$keys | string[] | Allowed metric keys. |
$is_strict | bool | Whether strict mode is active. |
Example:
Show code
add_filter( 'burst_allowed_metric_keys', function( $keys, $is_strict ) {
if ( $is_strict ) {
$keys[] = 'bounce_rate';
}
return $keys;
}, 10, 2 );
burst_allowed_metrics
Filters the full associative array of allowed metrics (key → label) after strict-mode filtering is applied. Core Geo_Statistics uses this hook to register the geo metrics (country_code, state, state_code, city, continent) in non-strict mode.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$metrics | array<string, string> | Allowed metrics map. |
$is_strict | bool | Whether strict mode is active. |
Example:
Show code
add_filter( 'burst_allowed_metrics', function( $metrics, $is_strict ) {
$metrics['my_metric'] = 'My Metric';
return $metrics;
}, 10, 2 );
burst_statistics_allowed_filter_keys
Filters the list of filter keys accepted by Statistics_Query.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$keys | string[] | Allowed filter keys. |
$is_strict | bool | Whether strict mode is active. |
Example:
Show code
add_filter( 'burst_statistics_allowed_filter_keys', function( $keys, $is_strict ) {
$keys[] = 'my_custom_filter';
return $keys;
}, 10, 2 );
burst_statistics_allowed_group_by
Filters the list of accepted group_by values.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$group_by | string[] | Allowed group-by fields. |
$is_strict | bool | Whether strict mode is active. |
Example:
Show code
add_filter( 'burst_statistics_allowed_group_by', function( $group_by, $is_strict ) {
$group_by[] = 'my_custom_column';
return $group_by;
}, 10, 2 );
burst_statistics_allowed_order_by
Filters the list of accepted order_by values.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$order_by | string[] | Allowed order-by clauses (e.g. 'pageviews DESC'). |
Example:
Show code
add_filter( 'burst_statistics_allowed_order_by', function( $order_by, $is_strict ) {
$order_by[] = 'my_column DESC';
$order_by[] = 'my_column ASC';
return $order_by;
}, 10, 2 );
burst_allowed_metrics_labels
Filters the full map of metric keys to their translated display labels.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$labels | array<string, string> | Map of metric key → translated label. |
Example:
Show code
add_filter( 'burst_allowed_metrics_labels', function( $labels ) {
$labels['my_metric'] = __( 'My Metric', 'my-plugin' );
return $labels;
} );
burst_allowed_post_types
Filters the list of post types accepted as a valid value for the page_type filter in strict mode.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$post_types | array | Array of public post type slugs. Default: get_post_types( ['public' => true] ). |
Example:
Show code
add_filter( 'burst_allowed_post_types', function( $post_types ) {
$post_types[] = 'my_private_type';
return $post_types;
} );
burst_possible_filters_with_prefix
@deprecated Filter key → column mappings are now registered with Filter_Registry::register( $key, 'table.column' ) (free-tier mappings in Metric_Bootstrap and core Geo_Statistics, Pro mappings in the Pro Statistics class). Register a custom filterable column with Filter_Registry::register( 'my_custom_filter', 'statistics.my_custom_column' ). A filter whose column references a joined table alias automatically pulls in that named join.
burst_mappable_filters
@deprecated The set of filters resolved through lookup tables is now fixed (browser, browser_version, platform, device). To add a custom filterable column, register it with Filter_Registry::register() and reference its qualified SQL column directly.
burst_build_where_clause
@deprecated Use the burst_statistics_query action instead. Mutate the Statistics_Query object directly (add WHERE conditions via where(), where_group(), or set_custom_where()) from a callback hooked to burst_statistics_query.
burst_available_joins
@deprecated JOIN definitions are now registered with Join_Registry::register( $alias, [ 'table' => ..., 'on' => ..., 'type' => ..., 'depends_on' => [] ] ) for static joins, or Join_Registry::register_dynamic( $alias, $factory ) for joins whose SQL depends on the query (e.g. date-filtered subqueries). The locations join (used by the geo metrics and filters) is registered by core Geo_Statistics. Pull a registered join into a query with $qd->with( $alias ), which resolves any depends_on chain. Each join configuration has the shape:
Show code
[
'table' => 'burst_sessions', // table name without prefix, or raw subquery SQL
'on' => 'statistics.session_id = sessions.ID',
'type' => 'INNER', // INNER or LEFT
'depends_on' => [], // aliases that must be joined first
]
There is no built-in session_bounces join. Bounce metrics resolve directly against sessions.bounce and sessions.ID via the always-present sessions join.
burst_query_timeout_ms
Filters the foreground (REST/admin) query timeout in milliseconds applied as a MAX_EXECUTION_TIME optimizer hint on the queries executed by fetch(), fetch_row(), and fetch_var().
Parameters:
| Parameter | Type | Description |
|---|---|---|
$timeout_ms | int | Resolved foreground timeout (default 30 000 ms, or the value of the burst_query_timeout_ms option when it is positive). |
$qd | Statistics_Query | The query object that is about to run. |
Example:
Show code
add_filter( 'burst_query_timeout_ms', function( $timeout_ms, $qd ) {
if ( $qd->get_id() === 'top_pages_by_device' ) {
return 5000; // 5 s for this specific query family
}
return $timeout_ms;
}, 10, 2 );
burst_query_timeout_ms_background
Filters the background (cron) query timeout in milliseconds. Used instead of burst_query_timeout_ms whenever wp_doing_cron() is true, so aggregation and backfill jobs get more headroom than dashboard requests.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$timeout_ms | int | Resolved background timeout. Default 900 000 ms (15 min). |
$qd | Statistics_Query | The query object that is about to run. |
Example:
add_filter( 'burst_query_timeout_ms_background', function( $timeout_ms ) {
return 1800000; // 30 min for background queries on this site
} );
burst_query_results_cache_ttl
Filters the object-cache TTL, in seconds, used by fetch() and fetch_row() to store query results. Set to 0 to disable result caching for the query.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$ttl | int | Resolved TTL in seconds. Default 30 s; 300 s when the query's date range is longer than 30 days. The burst_query_results_cache_ttl option overrides the default when set to a non-negative integer. |
$qd | Statistics_Query | The query object whose result is about to be cached. |
Example:
Show code
add_filter( 'burst_query_results_cache_ttl', function( $ttl, $qd ) {
if ( $qd->get_id() === 'live_dashboard_widget' ) {
return 0; // never cache this query
}
return $ttl;
}, 10, 2 );
burst_query_single_flight_enabled
Filters whether single-flight deduplication is active for cached query results. By default it is enabled only when a shared external object cache is in use (wp_using_ext_object_cache() is true), because the local object cache is request-scoped and provides no cross-request coordination.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$enabled | bool | Whether single-flight is enabled for this query. |
$qd | Statistics_Query | The query object that is about to run. |
Example:
Show code
add_filter( 'burst_query_single_flight_enabled', function( $enabled, $qd ) {
if ( $qd->get_id() === 'expensive_compare_block' ) {
return true; // force single-flight even without an external cache
}
return $enabled;
}, 10, 2 );
burst_query_single_flight_wait_ms
Filters how long, in milliseconds, follower requests wait for the leader query to populate the cache before returning an empty result. Lower values reduce tail latency under contention; higher values increase cache-hit yield.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$wait_ms | int | Resolved wait in milliseconds. Default 1200. |
$qd | Statistics_Query | The query object that is being followed. |
burst_query_single_flight_lock_ttl
Filters the TTL (in seconds) of the cache lock used to elect a single-flight leader for a given cache key. Defaults to ceil( $timeout_ms / 1000 ) + 2 with a floor of 5 seconds, so the lock always outlives the underlying MAX_EXECUTION_TIME.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$lock_ttl_sec | int | Resolved lock TTL in seconds. |
$qd | Statistics_Query | The query object that is about to run. |
$timeout_ms | int | Effective query timeout in milliseconds. |
burst_query_timeout_cooldown_ttl
Filters the cooldown TTL (in seconds) written to the object cache after a query times out. While the cooldown marker is present, repeat calls for the same cache key short-circuit and return null/[] without re-running the query, preventing the same expensive query from repeatedly hitting the database after a timeout.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$cooldown_ttl | int | Resolved cooldown TTL in seconds. Defaults to max( 30, ceil( $timeout_ms / 1000 ) ). |
$qd | Statistics_Query | The query object whose timeout is being recorded. |
$timeout_ms | int | Effective query timeout in milliseconds. |
Example:
add_filter( 'burst_query_timeout_cooldown_ttl', function( $cooldown_ttl ) {
return 120; // 2-minute cooldown after any timeout
} );
burst_object_cache_recommendation_threshold_seconds
Filters the slowest-query threshold (in seconds) that triggers the "persistent object cache recommended" admin task surfaced by should_recommend_object_cache(). The effective threshold is clamped to a 0.1 s floor.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$threshold_seconds | float | Threshold in seconds. Default 10.0. |
Example:
add_filter( 'burst_object_cache_recommendation_threshold_seconds', function() {
return 5.0; // recommend an object cache when any query exceeds 5 s
} );
Custom queries: end-to-end example
The following example queries pageviews and unique visitors grouped by page URL for the current month, filtered to desktop visitors only, and returns the top 10 results.
Show code
use Burst\Admin\Statistics\Statistics_Query;
$statistics = \Burst\burst_loader()->admin->statistics;
$start = strtotime( 'first day of this month midnight' );
$end = time();
$qd = Statistics_Query::create( 'top_desktop_pages_this_month' )
->date_range( $start, $end )
->select( [ 'page_url', 'pageviews', 'visitors' ] )
->filters( [ 'device' => 'desktop' ] )
->group_by( 'page_url' )
->order_by( 'pageviews DESC' )
->limit( 10 );
$rows = $qd->fetch( ARRAY_A );
foreach ( $rows as $row ) {
printf(
"%s — %s pageviews, %s visitors\n",
$row['page_url'],
$statistics->format_number( (int) $row['pageviews'] ),
$statistics->format_number( (int) $row['visitors'] )
);
}
Adding a custom WHERE clause (admin context only)
Show code
$qd = Statistics_Query::create( 'blog_pageviews_this_month' )
->date_range( $start, $end )
->select( [ 'pageviews', 'page_url' ] )
->group_by( 'page_url' )
->where( 'statistics.page_url', '%/blog/%', 'LIKE' );
$rows = $qd->fetch( ARRAY_A );
For a raw fragment, use set_custom_where() with prepared parameters:
$qd->set_custom_where( 'AND statistics.page_url LIKE %s', [ '%/blog/%' ] );
set_custom_where(), select_raw(), where_raw(), and having_raw() are only processed for users with admin access (non-strict mode). In strict mode these are silently discarded. All custom SQL is validated against a blocklist of dangerous keywords and patterns before use.