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.


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 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 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.