Skip to main content

Reports

The reporting system in Burst Statistics allows you to create, schedule, and send analytics reports via email. Reports can be sent on a daily, weekly, or monthly schedule, and support two formats: a classic email with data tables, and a "story" format with a link to a shareable dashboard.


Overview

Reports are stored in the {prefix}burst_reports database table. Each report has a set of content blocks, a list of email recipients, a frequency, and a delivery schedule. When a scheduled report is due, it is dispatched via WP-Cron using a batched email queue, with delivery status tracked in a separate {prefix}burst_report_logs table.

Pro - CreatorAvailable in the Creator tier

The full reporting system (scheduled reports, multiple formats, content blocks, delivery logs) is a Pro feature.

Learn more about automated reporting exports


Database tables

{prefix}burst_reports

Stores report configuration.

ColumnTypeDescription
IDint unsignedAuto-incremented primary key
namevarchar(255)Report display name
date_rangevarchar(255)The date range used for report data
formatvarchar(32)Report format (classic or story)
frequencyvarchar(16)Send frequency (daily, weekly, monthly)
fixed_end_datevarchar(16)Fixed end date (Y-m-d) used as the data range end
day_of_weekvarchar(16)Day of week for weekly/monthly reports (e.g. monday)
week_of_monthintOccurrence of the weekday in the month (1–4, or -1 for last)
send_timevarchar(16)Time to send the report in HH:MM format (site timezone)
last_editint unsignedUnix timestamp of last modification
enabledtinyint(1)Whether the report is active
scheduledtinyint(1)Whether the report is sent automatically on schedule
contentlongtextJSON-encoded array of content block definitions
recipientslongtextJSON-encoded array of recipient email addresses

{prefix}burst_report_logs

Tracks report delivery history.

ColumnTypeDescription
IDbigint unsignedAuto-incremented primary key
report_idbigint unsignedAssociated report ID
queue_idvarchar(32)Date-based queue identifier (Y-m-d, or test-Y-m-d-{timestamp} for test sends)
batch_idint unsignedBatch number (null for parent/queue-level log entries)
statusvarchar(32)Delivery status string
messagetextHuman-readable status message
timeint unsignedUnix timestamp of the log entry
datevarchar(10)Date string extracted from queue ID (Y-m-d)

Report formats

The format field on a report controls how the email is constructed.

ValueDescription
classicFull HTML email with data tables for each content block
storyShort teaser email with a button linking to a shareable story dashboard

Report frequency

The frequency field controls how often a scheduled report is sent.

ValueDescription
dailySent every day at the configured send_time
weeklySent on the configured day_of_week each week
monthlySent on the nth occurrence of day_of_week within each month, determined by week_of_month

For monthly reports, week_of_month accepts 14 for the first through fourth occurrence of the weekday, or -1 for the last occurrence.

Changed in v3.4.0: Weekly and monthly schedule matching now uses wp_date() in the site timezone instead of gmdate() in UTC. Reports configured for a specific weekday are now evaluated against the local calendar day, which prevents off-by-one mismatches near midnight UTC for sites with non-UTC timezones.


Report date ranges

The date_range (or reportDateRange) field on a report defines which period the report data covers.

Accepted values are defined by the Report_Date_Range domain type. The default when creating a report from onboarding is last_week.


Content blocks

Each report contains a content array of block definitions. For the classic format, these blocks map to data table sections rendered in the email.

Block objects stored in the content field have the following structure:

PropertyTypeDescription
idstringBlock identifier (must be a valid Report_Content_Block ID)
filtersarrayQuery filters applied to this block's data
contentstringOptional custom text content
date_rangestringOptional per-block date range override
date_range_enabledboolWhether the per-block date range override is active
fixed_end_datestringPer-block fixed end date (Y-m-d)
comment_titlestringOptional comment block title
comment_textstringOptional comment block body text

The compare block (identified by Report_Content_Block::COMPARE) is handled specially: it renders a summary table comparing pageviews, sessions, visitors, and bounce rate against the previous period.

When report block filters are sanitized through Query_Data, the call now uses an explicit query id (report_sanitize_content_filters) so that slow-query stats can attribute the work back to report sanitization.

Changed in v3.4.0: Query_Data instances must be constructed with an explicit string id as the first argument (e.g. new Query_Data( 'report_block_pages', $args )). Reports internally generate per-block ids in the form report_block_<key> when fetching block data.


Reporting field configuration

Reporting fields exposed to the admin UI are processed by Reporting_Fields::get(). Each field can declare optional notice, pro, and context sub-objects, which support a url key used by the UI to render help, upgrade, and context links.


Scheduling

The burst_every_hour cron hook triggers Reports::maybe_send_report(), which checks all enabled scheduled reports. A report is eligible to send when the current time falls within the report's scheduled send window for the day (within a 24-hour grace window after the scheduled timestamp). Eligible reports are dispatched via wp_schedule_single_event using the burst_send_email_batch hook, scheduled 5 minutes in the future.

Actual email sending happens in Reports::handle_email_batch(), which processes one batch of up to 10 recipients per cron run. If a report has more recipients than the batch size, additional batches are automatically scheduled 5 minutes apart.

Changed in v3.4.0: Reports::maybe_send_report() now exits early if the {prefix}burst_reports table does not exist. This prevents fatal errors on freshly installed sites where table creation has not yet completed when the cron fires.

Changed in v3.4.0: Each eligible report is now guarded by a per-report transient (burst_report_sent_{report_id}, 24-hour TTL) that is set before dispatching. If two burst_every_hour cron events fire in parallel for the same report (for example on sites with overlapping cron runs or external pingers), only the first one will dispatch the report — the second will skip it. This prevents duplicate sends to recipients.


REST API endpoints

GET /wp-json/burst/v1/reports

Returns all reports, ordered by last edit date. Requires manage-level access and a valid burst_nonce nonce parameter.

Changed in v3.4.0: This endpoint now performs an explicit manage_burst_statistics capability check before validating the nonce. Requests from users without manage permission receive a JSON error response instead of report data.

Response:

Show code
{
"request_success": true,
"data": {
"reports": [ { ... } ]
}
}

POST /wp-json/burst/v1/do_action/report/{action}

Performs a write action on a report. Requires admin access. The {action} segment maps to one of the following actions (prefixed with report- internally):

Action pathDescription
createCreate a new report
deleteDelete an existing report
updateUpdate an existing report
send-test-reportSend a test email immediately
send-report-nowSend the report immediately and schedule the queue
previewGet a preview HTML render of the report

Changed in v3.4.0: When send-test-report is used, a parent log entry with status processing is now inserted into {prefix}burst_report_logs (using the test-Y-m-d-{timestamp} queue id) before the test batch is handled, mirroring the logging behavior of scheduled sends. This ensures test send queues appear in the delivery log immediately, with the same parent/child structure used for resolving final queue status.

GET /wp-json/burst/v1/report/logs

Returns aggregated delivery logs for the past 30 days. Requires manage-level access and a valid burst_nonce nonce parameter.

Response:

Show code
{
"request_success": true,
"data": {
"logs": [ { ... } ]
}
}

POST /wp-json/burst/v1/get_action/story-report-data

Returns the report payload used to render a shareable story page. The request must include a valid share token in the action data; the handler validates the token against the stored share-token store before returning anything.

:::caution Breaking change Changed in v3.4.2: The internal action handler was renamed from report-data to story-report-data, and the token is now validated via Share::validate_share_token() before any data is returned. The handler also resolves share links exclusively through Share->tokens->get_share_links() and constructs the underlying Report with the shared-link flag (new Report( $report_id, true )), so scheduling and recipient fields are always omitted from story responses.

Any external consumer calling the previous report-data action must update its action name to story-report-data and supply the share token. :::

Request data:

FieldTypeDescription
tokenstringA valid 32-character hex share token bound to a report

Response: A Report::to_array() payload built with $is_shared_link = true, or an empty array when the token is missing or invalid.


Story share URLs

Story share URLs are generated by Reports::get_story_url() from the share token associated with the report.

Changed in v3.4.2: Story URLs are now first-class path routes under /burst-dashboard/story/ instead of hash-based routes on /burst-dashboard/. The token is still passed as the burst_share_token query parameter.

VersionURL format
Before v3.4.2https://example.com/burst-dashboard/?burst_share_token={token}#/story
v3.4.2 and laterhttps://example.com/burst-dashboard/story/?burst_share_token={token}

:::caution Breaking change Changed in v3.4.2: Any external system that constructs or matches story URLs by the old hash-based path must be updated to use the path-based format. The scheme is still normalized against BURST_URL to keep links consistent when generated from cron. :::

Internally, story share links are now retrieved through the Share->tokens->get_share_links() service rather than the legacy Share::get_share_links() method. Code that consumes the share-link API from outside the Reports module should call the new service method directly.


Delivery status values

The following status values appear in the burst_report_logs table and are returned in the lastSendStatus field of a report object.

StatusDescription
processingQueue has been created and email sending is scheduled
sending_successfulAll recipients received the email
sending_failedAll emails in the batch failed to send
partly_sentSome emails were sent, some failed
cron_missThe scheduled cron event did not fire within the expected window
scheduledReport is enabled and scheduled but has not sent yet
ready_to_shareReport is not scheduled; the shareable story link is ready
conceptReport is scheduled but currently disabled

:::caution Breaking change Changed in v3.3.0: The status value ready_to_send has been replaced by ready_to_share. The condition that triggers this status has also changed: previously it was returned only for reports with format = story; it is now returned for any report where scheduled = false, regardless of format.

Any consumer of the reports REST response that checks for "lastSendStatus": "ready_to_send" must be updated to check for "ready_to_share" instead. :::


Hooks and filters

burst_email_blocks

Filters the list of available content blocks used to populate the classic email report.

Parameters:

ParameterTypeDescription
$blocksarrayArray of block definitions loaded from includes/Admin/Mailer/config/blocks.php

Example:

Show code
add_filter( 'burst_email_blocks', function( array $blocks ): array {
// Add a custom block definition.
$blocks['my_custom_block'] = [
'title' => __( 'Top Custom Metric', 'my-plugin' ),
'header' => [ __( 'Label', 'my-plugin' ), __( 'Value', 'my-plugin' ) ],
'url' => '#/statistics',
'query_args' => [
'metrics' => [ 'my_metric' ],
'type' => 'my_type',
],
];
return $blocks;
} );

burst_mail_report_limit

Filters the maximum number of rows returned per content block in a classic email report.

Parameters:

ParameterTypeDescription
$limitintNumber of rows. Default: 5

Example:

add_filter( 'burst_mail_report_limit', function( int $limit ): int {
return 10;
} );

burst_mail_report_results

Filters the raw query results for a content block before they are processed into the email table.

Parameters:

ParameterTypeDescription
$raw_resultsarrayArray of result rows from the statistics query
$qdQuery_DataThe query data object used for the block
$start_dateintUnix timestamp of the report period start
$end_dateintUnix timestamp of the report period end

Example:

Show code
add_filter( 'burst_mail_report_results', function( array $results, $qd, int $start, int $end ): array {
// Remove rows where the first column is empty.
return array_filter( $results, fn( $row ) => ! empty( reset( $row ) ) );
}, 10, 4 );

burst_mail_reports_blocks

Filters the fully built blocks array before it is passed to the Mailer for rendering in a classic report. This runs after each block's data has been fetched and formatted.

Parameters:

ParameterTypeDescription
$blocksarrayAssociative array of built block data keyed by block ID
$date_startintUnix timestamp of the report period start
$date_endintUnix timestamp of the report period end

Example:

Show code
add_filter( 'burst_mail_reports_blocks', function( array $blocks, int $start, int $end ): array {
// Remove the compare block from all reports.
unset( $blocks['compare'] );
return $blocks;
}, 10, 3 );

burst_email_template

Filters the path to the main HTML email template file.

Parameters:

ParameterTypeDescription
$pathstringAbsolute path to the email template file

Example:

add_filter( 'burst_email_template', function( string $path ): string {
return get_stylesheet_directory() . '/burst/email.html';
} );

burst_email_block_template

Filters the path to the HTML template used to render each content block within the email.

Parameters:

ParameterTypeDescription
$pathstringAbsolute path to the block template file

Example:

add_filter( 'burst_email_block_template', function( string $path ): string {
return get_stylesheet_directory() . '/burst/block.html';
} );

burst_email_readmore_template

Filters the path to the HTML template used to render the "read more" section at the bottom of the email.

Parameters:

ParameterTypeDescription
$pathstringAbsolute path to the read-more template file

Example:

add_filter( 'burst_email_readmore_template', function( string $path ): string {
return get_stylesheet_directory() . '/burst/read-more.html';
} );

burst_report_log_delete_threshold

Filters how many days of report delivery logs are retained before old entries are purged.

Parameters:

ParameterTypeDescription
$daysintNumber of days to retain logs. Default: 30

Example:

add_filter( 'burst_report_log_delete_threshold', function( int $days ): int {
return 90;
} );

burst_report_formats

Filters the list of valid report format identifiers.

Burst ships with classic by default. Pro features may register additional formats such as story by hooking into this filter.

Parameters:

ParameterTypeDescription
$formatsstring[]Allowed report format slugs.

Example:

Show code
add_filter( 'burst_report_formats', function( array $formats ): array {
$formats[] = 'my_custom_format';
return array_values( array_unique( $formats ) );
} );

burst_create_report_from_onboarding (action)

Fired during onboarding to automatically create a default weekly summary report for a given email address.

Parameters:

ParameterTypeDescription
$emailstringThe recipient email address for the onboarding report

The default report created by this action has the following settings:

SettingDefault value
Name"Weekly Summary"
Formatclassic
Frequencyweekly
Day of weekmonday
Send time09:00
Date rangelast_week
Enabledtrue
Scheduledtrue

burst_send_email_batch (action)

WP-Cron hook that fires to process a single batch of emails for a report queue.

Parameters:

ParameterTypeDescription
$report_idintThe report ID
$queue_idstringThe queue identifier (date string or test queue ID)
$batch_idint|nullThe batch number (1-based)
caution

Do not call or schedule burst_send_email_batch directly. Use the report-send-report-now REST action or the admin UI instead. Manually scheduling duplicate events for the same [$report_id, $queue_id, $batch_id] combination is a no-op because the system checks for existing scheduled events and already-processed queue entries before sending.


Email template variables

The main email template (email.html) supports the following placeholder tokens:

TokenDescription
{base}HTML <base> tag pointing to the site home URL
{title}Email heading (HTML allowed)
{logo}Light-mode logo image URL
{logo_dark}Dark-mode logo image URL
{message}Introductory message text (e.g. date range coverage)
{blocks}Rendered HTML for all content blocks
{read_more}Rendered HTML for the "read more" section
{sent_by_text}Footer unsubscribe / sender attribution text
{domain}Site home URL

Each block within the block.html template supports:

TokenDescription
{title}Block heading
{subtitle}Block subheading (e.g. "vs. previous week")
{table}Rendered HTML table rows
{url}"Learn more" link URL
{learn-more}"Learn more" button text

The read-more.html template supports:

TokenDescription
{title}Section heading
{message}Teaser text
{read_more_url}Call-to-action button URL
{read_more_text}Call-to-action button label

Report object fields (API)

The to_array() method on a Report instance returns the following fields, as used in REST API responses.

Changed in v3.3.0: The Report constructor now accepts a second parameter bool $is_shared_link = false. When true, scheduling and recipient fields are replaced with safe defaults in the returned array, and if the report is disabled to_array() returns an empty array [] instead.

Changed in v3.4.2: The story REST handler (get_action/story-report-data) now always constructs the Report with $is_shared_link = true, so story responses are guaranteed to omit scheduling and recipient fields regardless of caller.

Show code
// Instantiate a report for a shared-link context (omits scheduling/recipient data).
$report = new Report( $report_id, true );
$data = $report->to_array(); // Returns [] if report is disabled.

// Standard instantiation (full data, used in admin/REST contexts).
$report = new Report( $report_id );
$data = $report->to_array();
FieldTypeDescription
idint|nullReport ID
namestringReport name
formatstringReport format (classic or story)
enabledboolWhether the report is active
contentarrayArray of content block definitions
reportDateRangestringDate range identifier
fixedEndDatestringFixed end date (Y-m-d)
frequencystringReport frequency (omitted for shared-link requests; defaults to Report_Frequency::DEFAULT)
weekOfMonthint|nullWeek of month for monthly reports (omitted for shared-link requests)
dayOfWeekstring|nullDay of week for weekly/monthly reports (omitted for shared-link requests)
sendTimestringScheduled send time (HH:MM) (omitted for shared-link requests)
lastEditintUnix timestamp of last modification (omitted for shared-link requests)
scheduledboolWhether the report sends automatically (omitted for shared-link requests)
recipientsarrayArray of recipient email addresses (omitted for shared-link requests)
lastSendStatusstringStatus string from the most recent delivery log (omitted for shared-link requests)
lastSendMessagestringHuman-readable message for lastSendStatus (omitted for shared-link requests)

Changed in v3.3.0: For shared-link requests ($is_shared_link = true), the fields marked above are replaced with empty/default values and are not populated from the database. If the report's enabled flag is false, to_array() returns [].


Email delivery details

  • Emails are sent using WordPress's wp_mail() function with Content-Type: text/html; charset=UTF-8.
  • Before sending, the recipient domain is validated via DNS MX/A record lookup. Emails to domains with no valid DNS records are skipped and logged as failures.
  • Recipients are processed in batches of 10 (default). Each batch is scheduled as a separate burst_send_email_batch cron event, 5 minutes apart.
  • Duplicate batch processing is prevented: the system checks both existing cron events and the burst_report_logs table before scheduling or sending.
  • The fixed_end_date on a scheduled report is automatically updated to yesterday each time the report is sent, anchoring the data range end date.
  • Test reports (sent via the "Send test" button) are delivered immediately without scheduling a cron event, and their queue IDs use the format test-Y-m-d-{timestamp} to distinguish them from scheduled sends in the logs. Since v3.4.0, a processing parent log entry is inserted before the test batch is handled, so test sends produce the same parent/child log structure as scheduled sends.
caution

Report delivery depends on WP-Cron. On sites with infrequent traffic or disabled WP-Cron, scheduled reports may be delayed or missed entirely. Missed sends are recorded in the logs with the cron_miss status. Consider configuring a real system cron job to trigger wp-cron.php on a regular interval.

Database indexes

Changed in v3.4.0: The internal add_index() helper used by Report_Logs and Reports now accepts a bare table suffix (e.g. 'burst_report_logs', 'burst_reports') instead of a fully-prefixed table name. The helper resolves the WordPress table prefix internally and validates the suffix against the plugin's known table list. If you are extending Burst and calling $this->add_index() from your own code, drop the $wpdb->prefix from the first argument.