benecaster_feed_podcast_episode_tags
Filters the per-episode Podcasting 2.0 tag array before XML output. Pre-populated from episode meta. Add-ons receive the populated array and may add, override, or remove entries. All keys are optional.
| Key | Type | Podcast 2.0 tag | Populated by |
|---|---|---|---|
transcript |
{url: string, type: string, language?: string, rel?: string}, or a list of them |
podcast:transcript, one element per entry |
Benecaster, from the episode’s transcript URL (left out while the add-on that owns that URL is not loaded); Transcription Service add-on |
soundbites |
list<{start_time: float, duration: float, title?: string}> |
podcast:soundbite |
— |
feed_credits |
list<{name: string, role?: string, url?: string, image?: string}> |
podcast:person |
Guest Manager add-on |
images |
list<{href: string, alt?: string, width: int, height: int, type: string}> |
podcast:image, one element per entry |
— |
season |
{number: int, name?: string} |
podcast:season |
— |
chapters |
{url: string, type?: string} |
podcast:chapters |
Benecaster (Chapter Markers) |
images is a list, and an add-on assigning a single srcset string emits nothing. The Podcast Index spec retired <podcast:images srcset="…"> in favour of repeated <podcast:image> elements, so the key is a list and each entry becomes its own element. A string in this key is silently dropped — there is no error and no fallback, the artwork simply does not appear in the feed. Entries without an href are skipped individually.
Benecaster populates it with one entry per registered WordPress image size, largest width first, deduplicated by URL. alt comes from the attachment’s Alternative Text and is omitted entirely when blank rather than emitted as alt="", because an empty alt asserts the image is decorative. If you append entries, follow the same rule.
transcript can be a list. Each entry becomes its own <podcast:transcript>, in order, so an episode can carry a text/html transcript beside an SRT or VTT one. type is one of text/plain, text/html, application/srt, text/vtt or application/json, and rel is the spec’s captions. Benecaster fills in a single entry from the episode’s own transcript URL. To add one, turn it into a list rather than overwriting it: isset( $tags['transcript']['url'] ) ? [ $tags['transcript'] ] : …. Entries without a url are skipped, and an exact repeat of an earlier URL and type is emitted once.
$context: who the feed is being built for
The fourth argument lets you withhold a tag from one listener and not another, for example a transcript sold as a buy-up. Register your callback with four accepted arguments to receive it; a three-argument callback keeps working unchanged.
| Key | Type | Meaning |
|---|---|---|
tier_slug |
string | The slug of the copy being built: a tier, public, or a buy-up or grant slug. |
listener_slugs |
list of strings | Every slug the listener’s feed is built from: their tier (or public), then each buy-up and standalone grant they hold. |
anonymous |
bool | true when the request carried no valid feed token: the public feed, and an invalid or revoked token sent there. false for every token holder, followers included. |
Decide on listener_slugs, never on tier_slug. A listener with a tier and a buy-up is served one copy per slug, merged, and the merge keeps the tier’s copy of each episode. A rule asking “is this the buy-up’s own feed?” takes the tag away from every holder for every episode their tier also carries.
It fails closed. When no listener is known, listener_slugs is just [ tier_slug ] and anonymous is true, so a withhold unless held rule withholds. Never read a short list as “no restriction”.
For the audio variant a listener hears, call benecaster_get_audio_variant_slug( $episode_id, $context['tier_slug'] ). For a buy-up ID kept in your settings, benecaster_get_buyup_slug() gives the slug listener_slugs carries. Benecaster itself withholds nothing: transcript visibility is the Transcription add-on’s setting, or your own filter (see the recipe Make the transcript a buy-up perk).
Decide only from the episode, the show and $context, never the current user, the request or the clock. The feed cache is keyed by exactly those, so anything else is cached and served to the next listener. A rule kept in episode or show meta clears the show’s feed cache when it changes; a rule kept anywhere else must call do_action( 'benecaster_clear_feed_cache', $show_id ) when it changes. Keys may be added to $context later; none will be removed or change meaning.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
$tags |
array |
— | Per-episode Podcasting 2.0 tag array. See description for key reference. All keys optional. |
$episode_id |
int |
— | Episode post ID |
$show_id |
int |
— | Show post ID |
$context |
array |
— | Who the feed is being built for: tier_slug, listener_slugs and anonymous. See the description. |
Returns:
array<string, mixed>
Example
add_filter( 'benecaster_feed_podcast_episode_tags', function( $tags, $episode_id, $show_id ) {
// Inject a soundbite from post meta (start/duration stored as JSON).
$soundbite = get_post_meta( $episode_id, '_episode_soundbite', true );
if ( $soundbite ) {
$sb = json_decode( $soundbite, true );
$tags['soundbites'][] = [
'start_time' => (float) $sb['start'],
'duration' => (float) $sb['duration'],
'title' => $sb['title'] ?? '',
];
}
return $tags;
}, 10, 3 );
Need this built rather than just documented? See our services →