Shortcodes (Developer Reference)
Full attribute tables, output filters, and extension points for all Benecaster shortcodes. For customer-facing usage examples see Shortcodes Reference.
All output filters receive at minimum (string $html, int $show_id, array $atts) where $atts is the resolved attribute array. All filters are Free unless noted.
Subscriber Feeds & Playback
[benecaster_player]
Embeds an episode audio or video player inline. Renders the locked-state message when the current user does not have access to the episode.
Access: Free — no license check; renders in all contexts.
| Attribute | Type | Default | Description |
|---|---|---|---|
episode_id |
int | current episode post ID | Episode to play |
show_id |
int | — | If episode_id is omitted, plays the latest episode for this show |
style |
string | 'standard' |
Player style: standard, minimal, or card |
show_title |
bool | true |
Render the episode title above the player |
show_artwork |
bool | true |
Render episode artwork |
benecaster_player_output — ( string $html, int $episode_id, array $atts ): string
Fires before the rendered HTML is returned. $atts contains the resolved (not raw) shortcode attributes.
When the current user cannot access the episode, the player renders the locked-state message instead. Override it via [benecaster_locked_content_message](/docs/benecaster_locked_content_message/). Override the access decision itself via [benecaster_episode_is_accessible](/docs/benecaster_episode_is_accessible/).
// Wrap every player in an analytics data container
add_filter( 'benecaster_player_output', function( string $html, int $episode_id, array $atts ): string {
return '<div class="my-analytics-wrapper" data-ep="' . esc_attr( $episode_id ) . '">'
. $html
. '</div>';
}, 10, 3 );
[benecaster_subscribe]
Renders a subscription call-to-action with tier options. Tier cards fire benecaster_subscribe_tier_content — the canonical extension point for injecting tier-specific pricing or CTAs.
Access: Premium — renders nothing without an active license.
| Attribute | Type | Default | Description |
|---|---|---|---|
show_id |
int | — | Required. Show to subscribe to |
tiers |
string | all tiers | Comma-separated tier slugs to display |
style |
string | 'cards' |
Layout: cards, list, or minimal |
highlight_tier |
string | — | Tier slug to visually highlight as the recommended option |
benecaster_subscribe_output — ( string $html, int $show_id, array $atts ): string — Premium
benecaster_subscribe_tier_content action — ( string $tier_slug, int $show_id )
Fires inside each tier card. Hook here to output tier-specific content: price, a feature list, or a custom subscribe button.
add_action( 'benecaster_subscribe_tier_content', function( string $tier_slug, int $show_id ): void {
$bridge = benecaster_get_bridge( $show_id );
foreach ( $bridge->get_all_tiers() as $tier ) {
if ( $tier['slug'] !== $tier_slug ) {
continue;
}
if ( ! empty( $tier['price'] ) ) {
echo '<p class="tier-price">$' . esc_html( number_format( $tier['price'], 2 ) ) . '/mo</p>';
}
if ( ! empty( $tier['subscribe_url'] ) ) {
echo '<a class="tier-cta" href="' . esc_url( $tier['subscribe_url'] ) . '">Subscribe</a>';
}
break;
}
}, 10, 2 );
[benecaster_feed_url]
Displays the current subscriber’s personal RSS feed URL. Shows nothing to unauthenticated visitors or logged-in users with no active token for this show.
Access: Premium — requires an active license.
| Attribute | Type | Default | Description |
|---|---|---|---|
show_id |
int | — | Required |
format |
string | 'link' |
Display as link (anchor tag), text (plain text), or input (copyable input field) |
label |
string | 'Your podcast feed URL' |
Label text rendered above or alongside the URL |
benecaster_feed_url_output — ( string $html, int $show_id, array $atts ): string — Premium
Use to change the masked URL display format or inject additional copy around the token reset CTA.
When generating links to the subscriber account page elsewhere, use the benecaster_account_page_url filter rather than hard-coding the URL:
$account_url = apply_filters( 'benecaster_account_page_url', get_permalink( benecaster_get_account_page_id() ) );
[benecaster_episodes]
Renders an episode list or grid. Respects tier access — locked episodes show a teaser when show_locked=true.
Access: Free — no license check.
| Attribute | Type | Default | Description |
|---|---|---|---|
show_id |
int | — | Required |
limit |
int | 10 |
Number of episodes to render |
offset |
int | 0 |
Offset for manual pagination |
tier |
string | current user’s tier | Filter to episodes accessible at this tier slug |
style |
string | 'list' |
list or grid |
show_locked |
bool | true |
Show locked episodes with teaser; false to hide entirely |
orderby |
string | 'date' |
date or episode_number |
order |
string | 'DESC' |
ASC or DESC |
benecaster_episodes_output — ( string $html, int $show_id, array $atts ): string
Use to inject pagination controls, change the list wrapper markup, or add data attributes.
Marketing & Social Proof
[benecaster_subscriber_count]
| Attribute | Type | Default | Description |
|---|---|---|---|
show_id |
int | current show | Which show to count |
tier |
string | (all tiers) | Restrict count to one tier slug |
type |
string | paying |
paying — active tokens joined to is_free_tier = 0 tiers; total — paying + free-tier; free — free-tier only |
format |
string | full |
full (“1,247”) or compact (“1.2k”) |
minimum |
int | 10 |
Render nothing if count is below this value; set 0 to always render |
label |
string | (none) | Text appended after the formatted number |
Premium. Reads directly from benecaster_tokens. Returns empty string when count is below minimum — the shortcode renders nothing, not a zero.
benecaster_subscriber_count_output
add_filter(
'benecaster_subscriber_count_output',
function ( string $html, int $count, array $atts ): string {
// $count is the raw integer before formatting and minimum check.
// $html is empty string when count < minimum — respect it.
if ( $html === '' ) {
return $html;
}
// Wrap in an animated counter element.
return '<span class="js-counter" data-count="' . $count . '">' . $html . '</span>';
},
10,
3
);
Parameters: $html (string), $count (int) — raw integer before formatting, $atts (array).
[benecaster_episode_count]
| Attribute | Type | Default | Description |
|---|---|---|---|
show_id |
int | current show | Which show |
tier |
string | (all) | Count only episodes accessible to this tier slug |
label |
string | (none) | Text appended after the number |
Free.
benecaster_episode_count_output
Parameters: $html (string), $count (int), $atts (array). Returns empty string when no episodes found.
[benecaster_tier_price]
| Attribute | Type | Default | Description |
|---|---|---|---|
show_id |
int | — | Required. Show whose active bridge to look up the tier in |
tier |
string | — | Required. Internal tier slug to look up |
Premium. Outputs the raw price number as text (e.g. 15). Wrap in HTML for currency formatting:
From $[benecaster_tier_price show_id="1" tier="gold"]/month
→ From $15/month
Returns empty string if the tier slug is not found in the active bridge or price is unavailable (e.g. membership plugin uses a pricing model Benecaster cannot read).
Parameters: $output (string), $price (float|null), $atts (array). $price is the raw float from the bridge, or null when unavailable.
[benecaster_tier_name]
| Attribute | Type | Default | Description |
|---|---|---|---|
show_id |
int | — | Required. Show whose active bridge to look up the tier in |
tier |
string | — | Required. Internal tier slug whose display name to output |
Premium. Returns empty string if slug not found.
Parameters: $output (string), $name (string), $atts (array). $name is the raw name string from the bridge.
[benecaster_pricing_table]
| Attribute | Type | Default | Description |
|---|---|---|---|
show_id |
int | current show | Which show’s tiers to render |
exclude |
string | (none) | Comma-separated internal tier slugs to hide |
cta_text |
string | Subscribe |
Subscribe button label on each tier column |
cta_url_base |
string | auto-detected | Base URL for subscribe links; auto-detection reads from the active bridge’s signup URL |
Premium. Tiers where is_public_tier = 1 are excluded automatically — they have no subscribe button and do not belong in a pricing comparison.
All rendered elements carry CSS classes for styling. Classes follow the pattern benecaster-pricing-* — inspect the rendered HTML for the full list.
benecaster_pricing_table_output
add_filter(
'benecaster_pricing_table_output',
function ( string $html, array $tiers, int $show_id, array $atts ): string {
// $tiers: array of {slug, name, price, external_tier_id} — after exclude is applied.
// Replace or augment the rendered HTML.
return $html;
},
10,
4
);
Parameters: $html (string), $tiers (array), $show_id (int), $atts (array). Use $tiers to read per-column tier slugs when injecting feature list rows.
Content
[benecaster_show_description]
Outputs the show’s long or short description.
Access: Free — no license check.
| Attribute | Type | Default | Description |
|---|---|---|---|
show_id |
int | — | Required. Show to read the description from |
type |
string | 'long' |
long — main show description; short — the short description field |
benecaster_show_description — ( string $description, int $show_id, string $type ): string
Filters the raw description string before shortcode attribute processing. Use to apply transformations to the stored value — merge tag replacement, custom formatting — regardless of where the description is rendered.
benecaster_show_description_output — ( string $html, int $show_id, array $atts ): string
Filters the final rendered HTML.
[benecaster_episode_description]
Outputs the episode’s description (the post excerpt or full content).
Access: Free — no license check.
| Attribute | Type | Default | Description |
|---|---|---|---|
episode_id |
int | current episode post ID | Episode to read |
show_id |
int | — | If episode_id is omitted, falls back to the latest episode for this show |
benecaster_episode_description — ( string $description, int $episode_id ): string
Filters the raw description string before rendering.
benecaster_episode_description_output — ( string $html, int $episode_id, array $atts ): string
Filters the final rendered HTML.
[benecaster_latest_episode]
Renders a composite block for the show’s most recently published episode: artwork, title, description, and an audio player.
Access: Free — no license check.
| Attribute | Type | Default | Description |
|---|---|---|---|
show_id |
int | — | Required. Show to query |
episode_id |
int | (latest) | Override automatic latest-episode selection with a specific episode ID |
sections |
string | 'artwork,title,description,player' |
Comma-separated sections to render |
style |
string | 'card' |
Layout: card or minimal |
link_title |
bool | true |
Wrap the episode title in a link to the episode permalink |
title |
string | — | Override the rendered title text (does not change the episode title in the database) |
link_text |
string | 'Listen now' |
Label for the “listen now” link rendered below the player |
benecaster_latest_episode_output — ( string $html, int $episode_id, array $atts ): string
Filters the final rendered HTML.
[benecaster_member_thanks]
Renders a list of members to thank on a podcast episode — by default, new subscribers since the last episode. Useful in show notes, episode pages, or any content area where you acknowledge your supporters.
Access: Free — no license check.
| Attribute | Type | Default | Description |
|---|---|---|---|
show_id |
int | — | Required |
query_type |
string | 'random_cycling' |
Built-in strategies: new_since_last_episode, random_cycling, milestone. Add custom types via benecaster_member_thanks_query_types |
count |
int | 5 |
Maximum number of members to include |
tier |
string | — | Limit results to one tier slug; omit for all tiers |
format |
string | 'list' |
list (unordered list), prose (comma-separated sentence), names (bare comma-separated) |
episode_id |
int | — | When set and the Podcast Workflow add-on is active, outputs the accepted Member Thanks list for that specific episode rather than a live query |
benecaster_member_thanks_query_types — ( array $types ): array
Filters the available query strategy instances. Keys are type slugs; values implement BenecasterMemberThanksQueryInterface. Use to register custom query logic — types added here appear automatically in the Podcast Workflow tile’s dropdown. See [benecaster_member_thanks_query_types](/docs/benecaster_member_thanks_query_types/).
benecaster_member_thanks_output — ( string $html, array $members, array $atts ): string
Filters the final rendered HTML before it is returned. $members is the raw result array from the active query type. See [benecaster_member_thanks_output](/docs/benecaster_member_thanks_output/).
Episode Page
[benecaster_episode_nav]
Renders Previous / Next episode navigation links.
Access: Free — no license check.
| Attribute | Type | Default | Description |
|---|---|---|---|
episode_id |
int | current episode post ID | Episode to navigate from |
show_id |
int | — | Required when episode_id is not set on an episode post |
style |
string | 'standard' |
standard or minimal |
show_titles |
bool | true |
Render the adjacent episode title alongside the Previous / Next label |
prev_label |
string | 'Previous' |
Label for the Previous link |
next_label |
string | 'Next' |
Label for the Next link |
scope |
string | 'show' |
Navigation scope: show — across all show episodes; season — within the same season number |
benecaster_episode_nav_prev — ( WP_Post|null $episode, int $current_episode_id, array $atts ): WP_Post|null
Filters the resolved Previous episode. Return a different WP_Post to swap the target, or return null to suppress the Previous link entirely.
benecaster_episode_nav_next — ( WP_Post|null $episode, int $current_episode_id, array $atts ): WP_Post|null
Filters the resolved Next episode. Same signature and semantics as benecaster_episode_nav_prev.
benecaster_episode_nav_output — ( string $html, int $episode_id, array $atts ): string
Filters the final rendered HTML.
[benecaster_related_episodes]
Renders a list of related episodes from the same show.
Access: Free — no license check.
| Attribute | Type | Default | Description |
|---|---|---|---|
episode_id |
int | current episode post ID | Episode to find related episodes for |
show_id |
int | — | Required when episode_id is not set on an episode post |
query |
string | 'tag' |
Query strategy: tag, season, guest (requires Guest Manager add-on), latest |
count |
int | 4 |
Maximum number of related episodes to render |
style |
string | 'list' |
list or grid |
exclude_current |
bool | true |
Exclude the current episode from results |
title |
string | 'Related Episodes' |
Heading rendered above the list |
benecaster_related_episodes_query_types — ( array $types ): array
Filters the available query strategy slugs. Each entry is a slug => label pair. Use this to register a custom strategy; the slug is then a valid value for the query attribute. Implement result resolution via benecaster_related_episodes.
benecaster_related_episodes — ( array $episodes, int $episode_id, string $query, array $atts ): array
Filters the resolved array of related episode WP_Post objects before rendering. $query is the resolved strategy slug. Return a different array to substitute the result set — for any strategy, built-in or custom.
benecaster_related_episodes_output — ( string $html, int $episode_id, array $atts ): string
Filters the final rendered HTML.
To implement a custom query strategy, hook both filters:
// Register the strategy slug and label.
add_filter( 'benecaster_related_episodes_query_types', function( array $types ): array {
$types['topic'] = 'By topic';
return $types;
} );
// Supply results when the strategy is selected.
add_filter( 'benecaster_related_episodes', function( array $episodes, int $episode_id, string $query, array $atts ): array {
if ( $query !== 'topic' ) {
return $episodes;
}
return my_plugin_get_related_by_topic( $episode_id, (int) ( $atts['count'] ?? 4 ) );
}, 10, 4 );
Alternatively, implement the BenecasterRelatedEpisodesQueryInterface contract and register it:
interface BenecasterRelatedEpisodesQueryInterface {
public function get_slug(): string;
public function get_label(): string;
/** @return WP_Post[] */
public function query( int $episode_id, array $atts ): array;
}
benecaster_register_related_episodes_query( new MyTopicQuery() );
[benecaster_episode_share]
Renders social sharing buttons for an episode URL.
Access: Free — no license check.
| Attribute | Type | Default | Description |
|---|---|---|---|
episode_id |
int | current episode post ID | Episode to share |
show_id |
int | — | Required when episode_id is not set on an episode post |
style |
string | 'buttons' |
buttons or icons |
platforms |
string | all built-in | Comma-separated platform slugs: twitter, facebook, linkedin, email |
text |
string | episode title | Override the share text |
benecaster_episode_share_text — ( string $text, int $episode_id, array $atts ): string
Filters the share text before it is URL-encoded into sharing links. Fires before the text shortcode attribute is applied, so this filter controls the default; the text attribute takes precedence when set.
benecaster_episode_share_output — ( string $html, int $episode_id, array $atts ): string
Filters the final rendered HTML.
[benecaster_social_links]
Renders the show’s social profile links.
Access: Free — no license check.
| Attribute | Type | Default | Description |
|---|---|---|---|
show_id |
int | — | Required |
style |
string | 'icons' |
icons or list |
platforms |
string | all configured | Comma-separated platform slugs to include; others are suppressed |
label |
string | (none) | Text label rendered before the link row |
benecaster_show_social_links — ( array $links, int $show_id ): array
Filters the resolved array of social link entries before rendering. Each entry is an associative array with slug and url keys. Use to add, remove, or reorder links programmatically.
benecaster_social_links_output — ( string $html, int $show_id, array $atts ): string
Filters the final rendered HTML.
[benecaster_rss_link]
Renders the show’s public RSS feed URL as a styled link or badge.
Access: Free — no license check.
| Attribute | Type | Default | Description |
|---|---|---|---|
show_id |
int | — | Required |
style |
string | 'link' |
link (anchor tag) or badge (styled button with RSS icon) |
label |
string | 'RSS Feed' |
Link or button label text |
icon_size |
string | 'medium' |
Icon size when style=badge: small, medium, or large |
benecaster_rss_link_output — ( string $html, int $show_id, array $atts ): string
Filters the final rendered HTML.
[benecaster_platform_links]
Renders the show’s “listen on” platform badge links.
Access: Free — no license check.
| Attribute | Type | Default | Description |
|---|---|---|---|
show_id |
int | — | Required |
style |
string | 'badges' |
badges or list |
platforms |
string | all configured | Comma-separated platform slugs to include; others are suppressed. Does not affect display order |
Platform data is stored on the show via ShowMeta::PLATFORM_LINKS. Read or write it programmatically:
$links = $show_meta->get_platform_links(); // Returns array of {slug, url} entries with non-empty URLs only
$show_meta->set_platform_links( $links ); // Persists the full array
Display order matches the order of entries returned by get_platform_links().
benecaster_platform_links_output — ( string $html, int $show_id, array $atts ): string
Filters the final rendered HTML.
[benecaster_search]
Renders a frontend episode search form and results container for a show.
Access: Free — no license check.
| Attribute | Type | Default | Description |
|---|---|---|---|
show_id |
int | — | Required. Show to search within |
placeholder |
string | 'Search episodes…' |
Input placeholder text |
per_page |
int | 10 |
Number of results per page |
style |
string | 'standard' |
standard or minimal |
Search runs against the episode FULLTEXT index on post_title and post_content. Queries are executed via the Benecaster REST endpoint — no full-page reload. To bypass FULLTEXT with a custom implementation, use the benecaster_search_query filter — see Action Hooks Reference.
benecaster_search_output — ( string $html, int $show_id, array $atts ): string
Filters the rendered container HTML (the form and results wrapper). Results are injected client-side after load and are not present in the filtered HTML.
[benecaster_episode_link]
Renders an inline link to another episode. Inserted automatically by the TipTap editor’s @ mention picker in episode descriptions and production notes.
Access: Free — no license check.
| Attribute | Type | Default | Description |
|---|---|---|---|
episode_id |
int | — | Required. Episode to link to |
show_id |
int | — | Optional — used for validation only |
label |
string | Episode title | Link display text |
benecaster_episode_link_output — ( string $html, int $episode_id, array $atts ): string
Filters the final rendered anchor tag HTML. Use to add data attributes for analytics, apply a custom CSS class, or replace the default anchor. See [benecaster_episode_link_output](/docs/benecaster_episode_link_output/).
[benecaster_timestamp]
Renders an inline timestamp that seeks the nearest Benecaster player to that position when clicked. Inserted automatically by the TipTap editor’s # trigger.
Access: Free — no license check.
| Attribute | Type | Default | Description |
|---|---|---|---|
time |
int | — | Required. Timestamp in seconds |
episode_id |
int | — | Optional — for association metadata; does not affect rendering |
label |
string | Formatted HH:MM:SS | Display label for the timestamp |
benecaster_timestamp_output — ( string $html, int $time, array $atts ): string
Filters the final rendered HTML (typically a <button> or <span>). Use to change the display format, apply a custom style, or inject additional metadata for a custom player integration. See [benecaster_timestamp_output](/docs/benecaster_timestamp_output/).
Subscriber Account
[benecaster_account]
| Attribute | Type | Default | Description |
|---|---|---|---|
show_id |
int | (all shows) | Limit to one show’s subscription info; 0 or omit for all active subscriptions |
sections |
string | all |
Comma-separated sections to render: subscription, feed_url, token_reset, app_links, qr_code |
Free (requires login). Shows a login prompt to unauthenticated visitors; shows a “no subscriptions” message when logged in but no active tokens exist.
Each section renders via benecaster_get_template_part() — theme overrides apply. Extension point: benecaster_account_sections action fires after all core sections inside each subscription card.
Per-section before/after hooks (benecaster_before_account_feed_url, etc.) are available once the account page TemplateLoader refactor ships — see Action Hooks Reference.
[benecaster_qr_code]
| Attribute | Type | Default | Description |
|---|---|---|---|
show_id |
int | — | Required |
type |
string | feed |
feed — subscriber’s private feed URL (Premium, requires active license); signup — show’s public signup page URL (Free) |
size |
int | 200 |
Display size in pixels (clamped 80–1000). Controls the inline QR code size only — download file dimensions are fixed |
download |
bool | true |
Show download button(s) |
format |
string | both |
Which download button(s) to show: both, svg, or png |
Premium (type=feed) / Free (type=signup).
Download file specs (hardcoded, unaffected by size):
- SVG — 50 mm × 50 mm with 4-module quiet zone; print-ready
- PNG — 1000 × 1000 px (~300 DPI up to 8.4 cm)
For type=feed, the QR code renders only after the subscriber resets their token — the full token is never stored server-side; the QR graphic is generated client-side from the token returned by the reset endpoint.
Parameters: $html (string), $show_id (int), $atts (array). Filters the wrapper container HTML. The QR graphic itself is generated client-side and is not in the filtered HTML.
Premium (type=feed) / Free (type=signup).
Output Filter Summary
| Filter | Parameters | Free/Premium |
|---|---|---|
benecaster_player_output |
$html, $episode_id, $atts |
Free |
benecaster_subscribe_output |
$html, $show_id, $atts |
Premium |
benecaster_feed_url_output |
$html, $show_id, $atts |
Premium |
benecaster_qr_code_output |
$html, $show_id, $atts |
Premium/Free |
benecaster_episodes_output |
$html, $show_id, $atts |
Free |
benecaster_locked_content_message |
$html, $episode_id, $required_tier, $user_tier |
Free |
benecaster_upgrade_prompt_html |
$html, $episode_id, $show_id, $required_tier, $user_tier |
Free |
benecaster_subscriber_count_output |
$html, $count, $atts |
Premium |
benecaster_episode_count_output |
$html, $count, $atts |
Free |
benecaster_tier_price_output |
$html, $price, $atts |
Premium |
benecaster_tier_name_output |
$html, $name, $atts |
Premium |
benecaster_pricing_table_output |
$html, $tiers, $show_id, $atts |
Premium |
benecaster_show_description |
$description, $show_id, $type |
Free |
benecaster_show_description_output |
$html, $show_id, $atts |
Free |
benecaster_episode_description |
$description, $episode_id |
Free |
benecaster_episode_description_output |
$html, $episode_id, $atts |
Free |
benecaster_latest_episode_output |
$html, $episode_id, $atts |
Free |
benecaster_episode_nav_prev |
$episode, $current_episode_id, $atts |
Free |
benecaster_episode_nav_next |
$episode, $current_episode_id, $atts |
Free |
benecaster_episode_nav_output |
$html, $episode_id, $atts |
Free |
benecaster_related_episodes_query_types |
$types |
Free |
benecaster_related_episodes |
$episodes, $episode_id, $query, $atts |
Free |
benecaster_related_episodes_output |
$html, $episode_id, $atts |
Free |
benecaster_episode_share_text |
$text, $episode_id, $atts |
Free |
benecaster_episode_share_output |
$html, $episode_id, $atts |
Free |
benecaster_show_social_links |
$links, $show_id |
Free |
benecaster_social_links_output |
$html, $show_id, $atts |
Free |
benecaster_rss_link_output |
$html, $show_id, $atts |
Free |
benecaster_platform_links_output |
$html, $show_id, $atts |
Free |
benecaster_search_output |
$html, $show_id, $atts |
Free |
benecaster_member_thanks_query_types |
$types |
Free |
benecaster_member_thanks_output |
$html, $members, $atts |
Free |
benecaster_episode_link_output |
$html, $episode_id, $atts |
Free |
benecaster_timestamp_output |
$html, $time, $atts |
Free |
Recipes
Wrap the subscriber count in an animated counter
add_filter(
'benecaster_subscriber_count_output',
function ( string $html, int $count, array $atts ): string {
if ( $html === '' ) {
return $html; // Respect the minimum threshold.
}
return '<span class="js-counter" data-target="' . $count . '">' . $html . '</span>';
},
10,
3
);
The $count parameter gives the raw integer before formatting — embed it as data-target for a JavaScript counter animation library.
Add a feature list between tier columns in the pricing table
add_filter(
'benecaster_pricing_table_output',
function ( string $html, array $tiers, int $show_id, array $atts ): string {
$features = [
'basic' => [ 'RSS feed access', 'Email support' ],
'premium' => [ 'RSS feed access', 'Priority support', 'Early access' ],
];
// Inject feature rows after the price row in each column.
// $tiers contains {slug, name, price, external_tier_id} per rendered column.
foreach ( $tiers as $tier ) {
$list = $features[ $tier['slug'] ] ?? [];
// Build and inject HTML per column using $tier['slug'] as a class target.
}
return $html;
},
10,
4
);