Template Overrides (Developer Reference)
Benecaster renders episode pages, the episode archive, and subscriber account pages using PHP template parts. Every template part can be overridden from a child theme or parent theme — no plugin code required. Replacing a single file is enough to change any part of the output.
Override Path
Place an overriding file at:
wp-content/themes/[your-child-theme]/benecaster/[template-relative-path].php
The path mirrors the plugin’s own templates/ directory exactly. For example, to override the episode title:
Plugin default: wp-content/plugins/benecaster/templates/episode/title.php
Your override: wp-content/themes/my-child-theme/benecaster/episode/title.php
Resolution order: child theme → parent theme → plugin default. The first readable file found is used.
Episode Template Parts
All episode template parts receive at minimum $episode_id (int) and $show_id (int).
| Template file | Additional $vars |
Description |
|---|---|---|
episode/single.php |
— | Outer wrapper; loads all other episode parts via benecaster_get_template_part() |
episode/title.php |
— | Episode title and episode type badge (badge suppressed for Full type) |
episode/player.php |
$can_access (bool), $user_tier (string) |
Audio or video player; delegates to player-locked.php when $can_access is false |
episode/player-locked.php |
$can_access (bool), $user_tier (string), $required_tier (string) |
Locked state shown instead of the player; padlock, subscribe CTA, or upgrade prompt |
episode/description.php |
— | Episode show notes / description (HTML) |
episode/content.php |
$can_access (bool), $user_tier (string) |
Extended episode content; may show teaser when $can_access is false |
episode/guests.php |
— | Guest list (rendered when episode has guest data from Guest Manager add-on) |
episode/references.php |
$references (array) |
Show notes references grouped by group_id; each entry: {id, label, url, description, display_label, group_id} |
episode/custom-fields.php |
$field_groups (array) |
Custom field values; each group: {group, fields, values} |
episode/share.php |
$share_links (array) |
Share links; each entry: {label, url, class} |
episode/subscribe.php |
— | Subscribe CTA for non-subscribers; suppressed for subscribers |
episode/navigation.php |
$prev_episode (\WP_Post|null), $next_episode (\WP_Post|null) |
Previous/next episode links within the same show |
$user_tier is the subscriber’s internal tier slug, or an empty string when the visitor is not subscribed. $required_tier is the minimum tier required to access the episode, or an empty string when undetermined.
Archive Template Parts
All archive template parts receive at minimum $show_id (int).
| Template file | Additional $vars |
Description |
|---|---|---|
archive/archive.php |
— | Outer archive wrapper; loads header, episode cards, pagination |
archive/header.php |
$title (string), $description (string) |
Archive heading and show description |
archive/filters.php |
— | Sort and filter controls bar |
archive/episode-card.php |
$episode_id (int), $can_access (bool) |
Episode card outer wrapper; loads all card sub-parts |
archive/episode-card/artwork.php |
$episode_id (int), $can_access (bool) |
Episode card artwork image |
archive/episode-card/title.php |
$episode_id (int), $can_access (bool) |
Episode card title link |
archive/episode-card/meta.php |
$episode_id (int), $meta (array) |
Card meta: episode_number, season_number, duration, pub_date |
archive/episode-card/excerpt.php |
$episode_id (int), $can_access (bool) |
Episode card excerpt |
archive/episode-card/player.php |
$episode_id (int), $can_access (bool) |
Inline player in the card (mini player) |
archive/episode-card/locked.php |
$episode_id (int) |
Locked state overlay on card when $can_access is false |
archive/pagination.php |
$total_pages (int), $pagination_html (string|null) |
Pagination controls |
archive/no-results.php |
— | Empty state shown when no episodes match the current filters |
Show Page Template Parts
All show page template parts receive $show_id (int).
| Template file | Additional $vars |
Description |
|---|---|---|
show/single.php |
— | Outer show page wrapper; loads all other show parts |
show/header.php |
$title (string), $description (string), $artwork_url (string) |
Show title, artwork, and description |
show/subscribe-links.php |
$links (array) |
Platform subscribe links; each entry: {platform, url} |
show/tiers.php |
$tiers (array) |
Tier listing from the active bridge |
show/episode-list.php |
$episodes (WP_Query) |
Embedded episode listing |
show/stats.php |
$episode_count (int) |
Public stats block (hidden by default; enable via benecaster_show_stats_visible filter) |
Global Template Parts
Locked state templates receive at minimum $episode_id (int) and $show_id (int).
| Template file | Additional $vars |
Description |
|---|---|---|
global/locked-message.php |
$required_tier (string), $user_tier (string) |
Locked state shown when a visitor cannot access an episode; renders padlock indicator + either upgrade prompt (wrong tier) or login prompt (not logged in) |
global/upgrade-prompt.php |
$required_tier (string), $user_tier (string) |
Upgrade CTA shown inside the locked state to logged-in subscribers at the wrong tier |
global/login-prompt.php |
— | Login/subscribe CTA shown inside the locked state to logged-out visitors |
Account Template Parts
All account template parts receive $show_id (int) and $user_id (int).
| Template file | Additional $vars |
Description |
|---|---|---|
account/subscription.php |
$subscription (array) |
Subscription summary card; $subscription includes tier name, status, join date |
account/feed-url.php |
$feed_url (string), $token_prefix (string) |
Feed URL with Copy button; $token_prefix is first 8 chars for display only |
account/qr-code.php |
$feed_url (string) |
QR code for the subscriber’s feed URL (generated client-side) |
account/app-links.php |
$app_links (array) |
Deep links to podcast apps; each entry: {app, label, url} |
account/token-reset.php |
— | Self-service token reset button and confirmation |
Template Functions
benecaster_get_template_part( $template, $vars = [], $echo = true )
Load a template part with child theme → parent theme → plugin default resolution.
// Load and echo a template part (typical usage in another template):
benecaster_get_template_part( 'episode/title', [
'episode_id' => $episode_id,
'show_id' => $show_id,
] );
// Load and return the rendered HTML:
$html = benecaster_get_template_part( 'episode/title', [
'episode_id' => $episode_id,
'show_id' => $show_id,
], false );
Variables in $vars are extracted into the template’s local scope — $vars['episode_id'] becomes $episode_id inside the template file.
benecaster_locate_template( $template )
Check whether a theme override exists for a template part. Returns the absolute path of the override file if found, or false if the plugin default will be used.
$override = benecaster_locate_template( 'episode/title' );
if ( $override ) {
// A theme file is overriding this template part.
}
benecaster_get_template_path( $template )
Return the full path of the file that would be loaded — override or plugin default. Returns an empty string when no file exists at any location.
$path = benecaster_get_template_path( 'episode/title' );
// '/var/www/html/wp-content/themes/my-child-theme/benecaster/episode/title.php'
// or the plugin's own default path if no theme override exists.
benecaster_get_header( $name = null )
Load the site header in a way that is compatible with both classic and block (FSE) themes. Use this in outer template files (single.php, archive.php) instead of calling get_header() directly.
- On block themes: renders the
headerblock template part viablock_template_part( 'header' ). - On classic themes with
header.php: callsget_header( $name )normally. - On themes without a header file: degrades silently — no output, no deprecation notice in the log.
Eliminating the deprecation log spam from FSE themes was the primary reason this helper was added. If you are overriding templates/episode/single.php, templates/show/single.php, or templates/archive/archive.php in your theme, replace any get_header() call with benecaster_get_header().
<?php benecaster_get_header(); ?>
<main class="benecaster-episode-single">
<?php benecaster_get_template_part( 'episode/title', [ 'episode_id' => $episode_id ] ); ?>
<?php // ... ?>
</main>
<?php benecaster_get_footer(); ?>
benecaster_get_footer( $name = null )
Counterpart to benecaster_get_header(). Use in outer template files instead of get_footer().
- On block themes: renders the
footerblock template part viablock_template_part( 'footer' ). - On classic themes with
footer.php: callsget_footer( $name )normally. - On themes without a footer file: degrades silently.
Two Customisation Layers
Template overrides replace whole files. When you only need to change a value, tweak markup, or inject content without replacing a template, use the complementary hook layers instead:
Before/after action hooks fire around every rendered section:
// Inject content before the episode navigation without overriding navigation.php:
add_action( 'benecaster_before_episode_navigation', function ( int $episode_id, int $show_id ): void {
echo '<hr class="my-divider">';
}, 10, 2 );
Output filters intercept specific values before they reach the template:
// Modify the share links array without touching episode/share.php:
add_filter( 'benecaster_episode_share_links', function ( array $links, int $episode_id ): array {
$links[] = [
'label' => 'Copy link',
'url' => '#',
'class' => 'benecaster-share-copy js-copy-link',
];
return $links;
}, 10, 2 );
See Filter Hooks Reference and Action Hooks Reference for the full catalog.
Templates Management REST Endpoints
The Settings → Templates screen is backed by four REST endpoints. Use these for headless setups, add-ons that bundle a custom template, or custom admin UI.
| Endpoint | Purpose |
|---|---|
GET /benecaster/v1/templates |
List all installed templates; response includes slug, name, preview_url, active (bool), built_in (bool) |
POST /benecaster/v1/templates/upload |
Upload a template ZIP (multipart/form-data, field name template_zip) |
POST /benecaster/v1/templates/{slug}/activate |
Activate an installed template for the current show |
DELETE /benecaster/v1/templates/{slug} |
Remove an installed template; returns 403 when built_in: true |
All endpoints require manage_options capability and a valid WP REST nonce. Built-in templates (Default Light, Default Dark) are always present and cannot be deleted.
Recipes
Override a single episode template part from a child theme
Copy the plugin’s template file to your theme’s benecaster/ directory at the same relative path. The override activates automatically — no PHP needed.
// Copy: wp-content/plugins/benecaster/templates/episode/title.php
// To: wp-content/themes/my-child/benecaster/episode/title.php
// Edit the copy. Benecaster will use your version on the next page load.
Confirm it’s active: benecaster_locate_template( 'episode/title' ) returns the path of your file when the override is in effect.
Load a custom template part from an add-on
Add-ons that render HTML can use benecaster_get_template_part() to allow theme developers to override their output:
// In your add-on's shortcode callback:
echo benecaster_get_template_part( 'my-addon/player-widget', [
'episode_id' => $episode_id,
'show_id' => $show_id,
], false );
// Theme override path: [theme]/benecaster/my-addon/player-widget.php
For add-on templates with their own templates directory, instantiate TemplateLoader directly with a custom plugin_templates_dir.
Check episode access in a shortcode or widget
$episode_id = get_the_ID();
$show_id = (int) get_post_meta( $episode_id, '_benecaster_show_id', true );
if ( benecaster_user_can_access_episode( $episode_id, $show_id ) ) {
echo '<audio src="' . esc_url( get_post_meta( $episode_id, '_benecaster_audio_url', true ) ) . '" controls></audio>';
} else {
$tier = benecaster_get_user_tier_for_show( $show_id );
if ( $tier !== '' ) {
echo '<p>' . esc_html__( 'Upgrade your subscription to access this episode.', 'my-addon' ) . '</p>';
} else {
echo do_shortcode( '[benecaster_subscribe show_id="' . $show_id . '"]' );
}
}
benecaster_user_can_access_episode() returns true for public episodes regardless of login status. Use benecaster_get_user_tier_for_show() to distinguish “wrong tier” from “not subscribed.”
Replace the episode share links with a custom set
add_filter(
'benecaster_episode_share_links',
function ( array $links, int $episode_id, int $show_id ): array {
$permalink = get_permalink( $episode_id );
$title = rawurlencode( get_the_title( $episode_id ) );
return [
[
'label' => 'Share on Threads',
'url' => 'https://www.threads.net/intent/post?text=' . $title . '%20' . rawurlencode( $permalink ),
'class' => 'benecaster-share-threads',
],
];
},
10,
3
);
Inject a “Listen now” button into every archive episode card
add_action(
'benecaster_after_episode_card_title',
function ( int $episode_id, int $show_id ): void {
?>
<a href="<?php echo esc_url( get_permalink( $episode_id ) ); ?>"
class="benecaster-card-cta benecaster-btn benecaster-btn--sm">
<?php esc_html_e( 'Listen now', 'my-theme' ); ?>
</a>
<?php
},
10,
2
);
Hide the sort controls on the episode archive
add_filter( 'benecaster_archive_show_filters', '__return_false' );
Useful when the archive is embedded in a layout where filtering is handled externally.
Recipe: customize-upgrade-prompt — Customize the upgrade prompt for a specific show
Use the benecaster_upgrade_prompt_html filter when you only need to change copy or add a link. Override global/upgrade-prompt.php when you need full HTML control.
Filter approach (copy/link changes only):
add_filter(
'benecaster_upgrade_prompt_html',
function ( string $html, int $episode_id, int $show_id, string $required_tier, string $user_tier ): string {
// Only modify for a specific show.
if ( $show_id !== 42 ) {
return $html;
}
return sprintf(
'<p class="benecaster-upgrade">This episode is for %s subscribers. <a href="/subscribe/">Upgrade your plan →</a></p>',
esc_html( $required_tier )
);
},
10,
5
);
Template override approach (full HTML replacement):
// Copy: wp-content/plugins/benecaster/templates/global/upgrade-prompt.php
// To: wp-content/themes/my-theme/benecaster/global/upgrade-prompt.php
Inside the override, $episode_id, $show_id, $required_tier, and $user_tier are available as local variables. Use the filter approach when possible — it composes with other filters; the template override replaces the entire output.
Recipe: inject-show-page-section — Add a custom section to the show page
Use benecaster_after_show_episode_list to inject content below the episode listing without touching a template file:
add_action(
'benecaster_after_show_episode_list',
function ( int $show_id ): void {
$newsletter_url = get_post_meta( $show_id, '_my_newsletter_url', true );
if ( ! $newsletter_url ) {
return;
}
?>
<div class="my-show-newsletter">
<h3>Stay in the loop</h3>
<p>Get new episode alerts in your inbox.</p>
<a href="<?php echo esc_url( $newsletter_url ); ?>" class="benecaster-btn">
<?php esc_html_e( 'Subscribe to the newsletter', 'my-plugin' ); ?>
</a>
</div>
<?php
},
10,
1
);
To inject content in a different position, swap the hook: benecaster_after_show_header, benecaster_after_show_subscribe_links, benecaster_after_show_tiers, or benecaster_after_show_stats. Each fires with $show_id as its only argument.
Recipe: add-account-page-section — Add a custom section to the subscriber account page
Use benecaster_after_account_qr_code to append a section after the built-in account content. The action fires with $show_id and $user_id.
add_action(
'benecaster_after_account_qr_code',
function ( int $show_id, int $user_id ): void {
// Example: link to the subscriber's episode history (custom feature).
$history_url = add_query_arg(
[ 'show' => $show_id, 'subscriber' => $user_id ],
home_url( '/episode-history/' )
);
?>
<div class="my-account-history">
<h3>Your listening history</h3>
<a href="<?php echo esc_url( $history_url ); ?>">View episodes you've accessed →</a>
</div>
<?php
},
10,
2
);
To insert before a built-in section instead, use the corresponding benecaster_before_* hook — for example, benecaster_before_account_subscription fires before the subscription summary card. All account hooks pass $show_id and $user_id as arguments.