Add a Custom Section to the Analytics Digest
Add your add-on’s data to any Benecaster Analytics Digest (daily, weekly, or monthly) by injecting a data payload via benecaster_analytics_digest_data and registering a custom section via benecaster_analytics_digest_sections. This is the standard integration pattern used by Listener Support (donation totals), Sponsor Manager (click performance), and any other add-on that wants to contribute content to the digest email.
When to Use This
Use these hooks when your add-on tracks metrics a podcaster would want to see alongside their subscriber numbers in a periodic report — donation totals, sponsor click-throughs, community sign-ups, affiliate revenue, anything episode- or audience-related.
The Hooks
Each hook has a shared variant that fires for all digest frequencies and a type-specific variant that fires for one frequency only. The shared filter fires first; the type-specific variant fires after it.
| Hook | Fires for | Parameters |
|---|---|---|
benecaster_analytics_digest_data |
All types | $data, $type, $show_id, $period |
benecaster_analytics_digest_sections |
All types | $sections, $type, $show_id, $data |
Each of those has three frequency-specific companions, named by appending _daily, _weekly, or _monthly. They carry one fewer argument — $type is dropped, because the hook name already says it — so a daily data callback receives $data, $show_id, $period.
Reach for a frequency-specific hook when your callback only ever applies to one cadence, and the shared one when a single callback handles several and needs to branch. Both linked pages spell out their own variants.
Prerequisites
-
Benecaster installed with a valid license
-
Analytics Dashboard add-on active
-
Your plugin or add-on loaded before the digest cron runs
How It Works
It is always two steps, in order.
Step 1 — inject data into the payload. Hook the data filter for the frequency you’re targeting, guard on the add-on being active, run your query using the supplied show ID and period, and assign the result into the data array under a prefixed key.
Step 2 — register a section that renders it. Hook the matching sections filter. The data array arriving here already contains the key you set in step 1. Bail without adding anything when that key is empty, then append a section definition with id, title, and either lines (a list of plain-text strings, which the email escapes) or template (an absolute path to a PHP file in your add-on). data is free-form.
Using the Shared Hook for Multiple Frequencies
If your add-on contributes to more than one digest frequency and the query or section logic differs between them, use the shared filters and branch on the type argument — one callback each instead of one per frequency. The shared filters take four parameters where the type-specific ones take three.
Notes
The guard is required. Every callback on an add-on-specific hook must call benecaster_addon_is_active( 'analytics-dashboard', $show_id ) before doing anything: guard on benecaster-addon-analytics-dashboard as the hook entries do, as well as on anything else your section reads. This ensures your code degrades cleanly if the Analytics Dashboard add-on is deactivated later. The same guard pattern applies everywhere in Benecaster.
Section template. A template is included with $section (the whole section, its data included), $show_id and $type in scope. Its output goes through wp_kses_post(), so scripts and event handlers are removed, and a template that prints nothing leaves the section out.
Section ID prefix. Built-in section IDs are reserved: subscriber_snapshot, new_and_lost, episode_pulse, feed_engagement, cohort_highlight, benchmark_comparison, notable_callout. Prefix custom IDs with your add-on slug (my_addon_top_episodes, not top_episodes) to avoid collision with Benecaster and third-party add-ons.
Empty check before adding. If your data query returns nothing meaningful, don’t add the section. Every digest has subscriber_snapshot and new_and_lost (and weekly and monthly feed_engagement), and it is suppressed when it has no subscriber activity, no callout and no section beyond those. So a section you add is what makes a quiet digest send.
Monthly wins on overlap. If the podcaster’s monthly and weekly digest fall on the same calendar day, monthly is sent and weekly is skipped — it is not deferred. If your section only appears in the weekly template, it won’t appear on that day. Where meaningful, add a corresponding monthly section that summarizes the same data over the longer period rather than relying on the weekly to fill the gap.
The period is in UTC days, whatever the site’s time zone. start and end are inclusive UTC days (YYYY-MM-DD), timezone is always 'UTC', and start_at and end_before are those days already widened into UTC datetimes, half open: query >= start_at AND < end_before. Don’t widen start and end yourself in local time, or every digest files hours of activity under the wrong day. prior_start and prior_end are the period the digest compares against.
Where the donation figures come from. The example reads benecaster_get_listener_support_donations(), never Benecaster’s tables. Its rows carry amount_donated in minor units of each row’s own currency, or null for a donation with no amount, which is counted but never summed. Never add amounts across currencies.
Related
-
Analytics Dashboard Developer Reference — all hooks, filters, and recipes for this add-on
-
For Developers — add-on guard pattern and hook-first architecture overview
Code
<?php
add_filter( 'benecaster_analytics_digest_data_weekly', function ( array $data, int $show_id, array $period ): array {
if ( ! benecaster_addon_is_active( 'benecaster-addon-analytics-dashboard', $show_id ) ) {
return $data;
}
$data['my_addon_donations'] = benecaster_get_listener_support_donations(
$show_id,
[ 'date_from' => $period['start'], 'date_to' => $period['end'] ] // Inclusive UTC days, as the reader takes them.
);
return $data;
}, 10, 3 );
add_filter( 'benecaster_analytics_digest_sections_weekly', function ( array $sections, int $show_id, array $data ): array {
$rows = $data['my_addon_donations'] ?? [];
if ( [] === $rows ) {
return $sections; // Nothing to say: add nothing, so a quiet week stays unsent.
}
$by_currency = [];
foreach ( $rows as $row ) {
if ( null !== $row['amount_donated'] && null !== $row['currency'] ) {
$by_currency[ $row['currency'] ] = ( $by_currency[ $row['currency'] ] ?? 0 ) + (int) $row['amount_donated'];
}
}
$lines = [ sprintf( _n( '%s donation this week.', '%s donations this week.', count( $rows ), 'my-addon' ), number_format_i18n( count( $rows ) ) ) ];
foreach ( $by_currency as $currency => $minor ) {
$lines[] = sprintf( '%s %s', $currency, number_format_i18n(
\Benecaster\Membership\RevenueLedgerRepository::to_major_units( $minor, $currency ),
\Benecaster\Membership\RevenueLedgerRepository::decimals_for( $currency )
) );
}
$sections[] = [ 'id' => 'my_addon_donations', 'title' => __( 'Listener support', 'my-addon' ), 'lines' => $lines ];
return $sections;
}, 10, 3 );
Hooks Used
-
benecaster_analytics_digest_data -
benecaster_analytics_digest_sections -
benecaster_analytics_digest_data_weekly -
benecaster_analytics_digest_sections_weekly
Need this built rather than just documented? See our services →