Skip to main content

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.

Looking for email templates? They use the same override mechanism but a different directory, and they’re documented separately in Customizing Email Templates — which lists every email template, the variables each one receives, and the benecaster_email_wrapper_args filter that handles branding without requiring an override at all.


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 → registered add-on directories → plugin default. The first readable file found is used.

Your theme override wins over everything, add-ons included. An add-on can register its own template directory (see below), but it sits third in that list — it can replace one of Benecaster’s own defaults and it can never outrank a file you put in your theme. If you have overridden a part and it has stopped taking effect, an add-on is not the cause.


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/explicit-badge.php $episode_id (int), $show_id (int), $context (string), $show_clean (bool) Explicit-content badge; shared by the single page, archive cards, [benecaster_episodes], [benecaster_player], and [benecaster_explicit_badge]. Renders nothing for clean episodes unless $show_clean. See template-episode-explicit-badge
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/download.php $meta (EpisodeMeta) The “Download episode” link beneath the player. Fires benecaster_before_episode_download and benecaster_after_episode_download, each with ($episode_id, $show_id). Renders nothing when the episode has no audio URL.
episode/transcript.php $meta (EpisodeMeta) The “Read the transcript” link, straight after the download link. It links to the episode’s transcript file; it does not display the text. Fires benecaster_before_episode_transcript and benecaster_after_episode_transcript, each with ($episode_id, $show_id). Renders nothing when the episode has no transcript URL.
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

episode/download.php and episode/transcript.php are included by episode/single.php, and the parts themselves do not check access. single.php includes the download link only when the show’s Show a download link on episode pages setting is on and the visitor can play the episode, narrowed by the benecaster_show_episode_download filter. It includes the transcript link only when the episode has a transcript URL and the visitor can access the episode, narrowed by the benecaster_show_episode_transcript filter. A theme that overrides single.php must make the same decisions itself by calling BenecasterEpisodeEpisodePageLinkGate::shows_download() and shows_transcript() before including the parts, or the links never appear. Don’t include either part from anywhere else, because an override of it assumes the check already happened.

$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

Most account template parts receive $show_id (int) and $user_id (int). The wall name prompt receives $user_id only — the wall name is not show-scoped.

Template file Additional $vars Description
account/subscription.php $subscription (array) Subscription summary card; $subscription includes tier name, status, join date
account/buyups.php $buyups (array), $has_native (bool) The Add-ons section — one card per buy-up available to the subscriber’s tier on this show. Wrapped by the benecaster_before_account_buyups / benecaster_after_account_buyups actions, and contains the benecaster_buyup_soldout_display filter. The per-row shape is documented in the file header. See the upgrade note below before overriding this one.
account/billing-portal.php $sub (array) Billing management panel for subscribers on Benecaster-managed billing
account/feed-url.php $sub (array), $masked_url (string) Feed URL with Copy button; $masked_url shows the first 8 characters of the token only. Reports “This feed is not currently active…” with no URL when $sub['feed_is_dark'] is true.
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 $sub (array) Self-service token reset button and confirmation. Renders nothing when $sub['feed_is_dark'] is true — see note below.
account/wall-name-prompt.php $wall_name_prompt_prefill (string), $prompt_show_name (string) First-run overlay prompting new subscribers to set their wall name; shown once, then suppressed. Gated by benecaster_show_wall_name_prompt. Receives $user_id only — no $show_id.

account/avatar.php and account/dashboard.php are overridable the same way and have their own pages — see Account Avatar Template.

If you are looking for account/display-name.php or account/display-name-prompt.php, neither exists. The wall name field lives in account/avatar.php, and the prompt is account/wall-name-prompt.php, taking $wall_name_prompt_prefill. An override sitting at either of those other paths does nothing at all — silently, because a template that does not exist in the plugin is never looked for in your theme.


Template Functions

Each of these has a full reference entry in the function library — signature, parameters, return values, and examples.

benecaster_get_template_part( $template, $vars = [], $echo = true )

Load a template part with child theme → parent theme → registered add-on directories → 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.
}

This searches the child theme and parent theme only. It deliberately does not see registered add-on directories — an add-on directory is not a theme override, and this function answers the theme question. For “which file would actually load?”, use benecaster_get_template_path() below.


benecaster_get_template_path( $template )

Return the full path of the file that would be loaded — theme override, registered add-on directory, or plugin default. Returns an empty string when no file exists at any of the four locations.

$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_register_template_directory( $path )

For add-on authors. Registers a directory that Benecaster will search when resolving template parts, so an add-on can ship its own parts — both ones that replace a Benecaster default and ones Benecaster has never heard of. Call it from a benecaster_boot callback.

add_action( 'benecaster_boot', function (): void {
    benecaster_register_template_directory( plugin_dir_path( __FILE__ ) . 'templates' );
} );

The directory mirrors the plugin’s templates/ structure, so {$path}/guest/profile-card.php answers the template name guest/profile-card.

Registered directories sit third, behind both themes. A part shipped by an add-on is still fully overridable from your theme at {theme}/benecaster/guest/profile-card.php — registration does not change that, and add-on authors should treat any part they ship as a public template.

Among add-ons the order is registration order, which is add-on boot order — so two add-ons registering the same template name is not a supported arrangement.


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 header block template part via block_template_part( 'header' ).
  • On classic themes with header.php: calls get_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(); ?>

Counterpart to benecaster_get_header(). Use in outer template files instead of get_footer().

  • On block themes: renders the footer block template part via block_template_part( 'footer' ).
  • On classic themes with footer.php: calls get_footer( $name ) normally.
  • On themes without a footer file: degrades silently.

Two Customization 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

Worked examples for the patterns on this page live in the recipe library, each with
runnable code you can copy:

See Also

Need this built rather than just documented? See our services →