Skip to main content

Customizing Episode Listings

Benecaster’s episode-listing shortcodes — [benecaster_episodes], [benecaster_latest_episode], and [benecaster_related_episodes] — all share a common filter that lets you control how each item renders. A single callback covers all three surfaces.


Controlling what items render: benecaster_episode_item_output

The benecaster_episode_item_output filter fires once per rendered item across all three shortcodes. You receive the default HTML, the episode and show IDs, the current user’s tier slug, and whether the episode is locked for the current visitor. Return the HTML unchanged, return a replacement string, or return "" to suppress the item entirely.

add_filter( 'benecaster_episode_item_output', function ( string $html, int $episode_id, int $show_id, string $user_tier, bool $is_locked ): string {
    // $html      — the default rendered markup for this item
    // $episode_id — post ID of the episode
    // $show_id   — post ID of the show
    // $user_tier  — the current user's active tier slug, or '' if not subscribed
    // $is_locked  — true when the user cannot access this episode
    return $html;
}, 10, 5 );

Two common patterns:

Hide locked episodes entirely

Return an empty string for locked items — the slot is skipped and nothing renders in its place.

add_filter( 'benecaster_episode_item_output', function ( string $html, int $episode_id, int $show_id, string $user_tier, bool $is_locked ): string {
    return $is_locked ? '' : $html;
}, 10, 5 );

This works across all three shortcodes with one callback. Compare this to the show_locked="false" attribute on [benecaster_episodes], which only applies to that one shortcode instance and runs before this filter — so show_locked="false" is faster for a single shortcode, and this filter is more convenient when you want consistent behavior everywhere.

Replace locked items with a teaser card

Return custom markup for locked episodes so visitors can still discover the content:

add_filter( 'benecaster_episode_item_output', function ( string $html, int $episode_id, int $show_id, string $user_tier, bool $is_locked ): string {
    if ( ! $is_locked ) {
        return $html;
    }
    $title       = get_the_title( $episode_id );
    $description = get_post_meta( $episode_id, '_benecaster_episode_description_rss', true );
    $teaser      = wp_trim_words( wp_strip_all_tags( (string) $description ), 40, '…' );
    $subscribe_url = home_url( '/subscribe/?show=' . $show_id );
    ob_start();
    ?>
    <div class="my-episode-teaser">
        <h3><?php echo esc_html( $title ); ?></h3>
        <p><?php echo esc_html( $teaser ); ?></p>
        <a href="<?php echo esc_url( $subscribe_url ); ?>">Subscribe to hear this episode</a>
    </div>
    <?php
    return (string) ob_get_clean();
}, 10, 5 );

The $is_locked value reflects all access rules — including any rules registered by third-party code via benecaster_episode_is_accessible — so you don’t need to replicate access logic in this callback.


Customizing pagination output

When [benecaster_episodes paged="true"] is set, the shortcode renders a Previous / “Page N of M” / Next navigation strip below the list. Replace it entirely with the benecaster_episodes_pagination_output filter:

add_filter( 'benecaster_episodes_pagination_output', function ( string $html, WP_Query $query, array $atts ): string {
    $current = (int) ( $query->query_vars['paged'] ?? 1 );
    $max     = (int) $query->max_num_pages;

    if ( $current >= $max ) {
        return ''; // last page — no next link needed
    }

    $next_url = add_query_arg( 'ep_page', $current + 1 );
    return sprintf(
        '<div class="my-load-more"><a href="%s">Load more episodes</a></div>',
        esc_url( $next_url )
    );
}, 10, 3 );

The filter fires even on single-page results, so you can inject a custom strip unconditionally. Check $query->max_num_pages before building navigation to avoid rendering arrows that point nowhere.


Disambiguating multiple paged lists

When two [benecaster_episodes paged="true"] shortcodes appear on the same page, they both read ?ep_page=N by default — clicking Next on either list advances both simultaneously. Assign a unique query var per shortcode with benecaster_ep_page_query_var:

add_filter( 'benecaster_ep_page_query_var', function ( string $query_var, int $show_id, array $atts ): string {
    return 'ep_page_' . $show_id;
}, 10, 3 );

The shortcode’s Previous/Next links are updated automatically to use the new var name. If you also use benecaster_episodes_pagination_output to build custom navigation, use the same overridden var name in add_query_arg().


Further reading

See Also