Customizing Tenure Badges
Three filters give developers full control over how tenure badges are computed and rendered. They compose cleanly — each one fires at a different point in the pipeline, so you can use any combination without interference.
| Filter | Fires from | What you control |
|---|---|---|
benecaster_tenure_badge_milestones |
TenureBadgeCalculator::resolve_milestones() |
The threshold map — which month counts produce which labels |
benecaster_tenure_badge_label |
TenureBadgeCalculator::build_snapshot_from_row() |
The selected label, per subscriber, after the map is matched |
benecaster_tenure_badge_output |
BadgeChipRenderer::render_chip() |
The chip HTML for tenure_auto rows |
Two Computed Rows on benecaster_user_badges
The tenure chip described below isn’t the only row Benecaster appends to the badge list. Benecaster registers two callbacks on the benecaster_user_badges filter:
| Callback | Priority | source |
Fires when |
|---|---|---|---|
FollowerBadgeAppender::append_follower_badge |
15 | follower_auto |
A show is in context and the user’s token on it is a follower’s |
TenureBadgeAppender::append_tenure_badge |
20 | tenure_auto |
The tenure toggle is on and a snapshot resolves (see Subscriber Tenure Badges) |
Both are computed, synthesized rows — neither is a persisted badge grant, and neither can be manually assigned or removed from the admin UI. Register your own callback at a priority later than 15 if you want to see, or filter out, the Follower chip — anything registered at 15 or earlier runs before it exists in the array.
When a show is in context, the outer .benecaster-user-badges wrapper carries an is-follower or is-subscriber class plus a data-token-type attribute, and any chip whose row knows its token type — the Follower chip and the tenure chip both do — carries the same class. With no show in context (the account page’s site-wide render), none of this classification is added, because one person can follow one show while paying for another.
Redefine the Threshold Map
Filter: benecaster_tenure_badge_milestones
Replace the entire milestone map with your own month → label pairs. The calculator picks the highest threshold that does not exceed the subscriber’s tenure and uses that entry’s label.
add_filter( 'benecaster_tenure_badge_milestones', function ( array $default ): array {
return [
3 => 'First-Quarter Supporter',
6 => 'Half-Year Supporter',
12 => 'One-Year Anniversary',
24 => 'Two-Year Anniversary',
];
} );
Normalization: the calculator sorts your map ascending and drops entries with non-positive month keys or non-string / empty labels. Returning a non-array falls back to the built-in defaults. Return order does not matter.
Below-first-milestone subscribers: a subscriber whose tenure is below your lowest threshold receives an empty label — the chip is suppressed unless benecaster_tenure_badge_label returns a non-empty string for them.
Override the Matched Label
Filter: benecaster_tenure_badge_label
Fires after the milestone map is matched. Use this when the label needs to vary per subscriber in ways the threshold map alone cannot express: localisation, a special designation for very long-tenure members, or injecting a “New Supporter” label below the first milestone.
add_filter(
'benecaster_tenure_badge_label',
function ( string $label, int $user_id, int $show_id, int $months_since_join ): string {
// Charter Member designation for anyone past three years.
if ( $months_since_join >= 36 ) {
return __( 'Charter Member', 'my-addon' );
}
return $label;
},
10, 4
);
Inject a label for below-first-milestone subscribers:
add_filter(
'benecaster_tenure_badge_label',
function ( string $label, int $user_id, int $show_id, int $months_since_join ): string {
// $label is '' when tenure is below the lowest milestone threshold.
// Return a non-empty string to show a chip for new members.
return $label !== '' ? $label : __( 'New Supporter', 'my-addon' );
},
10, 4
);
$show_id notes:
- On per-show surfaces (Supporter Wall),
$show_idis the show being rendered. - On the site-wide account page, the calculator uses the earliest active token across all shows and passes that show’s ID as
$show_id. $show_id === 0only when the user has no active tokens at all.
Replace the Chip HTML
Filter: benecaster_tenure_badge_output
Fires from BadgeChipRenderer::render_chip() only for tenure_auto badge rows. Tier and manual badges are unaffected. Use this to wrap the chip, add a tooltip, link it to a page, or replace it with entirely custom markup.
add_filter(
'benecaster_tenure_badge_output',
function ( string $html, int $user_id, int $show_id ): string {
$tip = esc_attr__( 'Based on your subscription start date', 'my-addon' );
return '<span class="my-tenure-tooltip" data-tip="' . $tip . '">' . $html . '</span>';
},
10, 3
);
$show_id is 0 when the chip renders on the site-wide account page (per-user mode).
Composing All Three Together
The filters fire in order: milestones → label → output, each handing its result to the next. Running all three gives you complete control over the badge, from which tenures earn one through to the markup it renders as.
That full pipeline is a recipe rather than a snippet, because it is something you copy and adapt whole:
→ Customize tenure badges end to end — a quarterly milestone map, a “Charter Member” designation past three years, and a chip that links to an explainer page. Use whichever stages you need and drop the rest.
The Same Pattern: the Explicit-Content Badge
Tenure badges are not the only first-party badge Benecaster renders, and the second one follows the same shape — so what you learn here transfers.
The explicit-content badge marks episodes as explicit on the episode page, archive cards, [benecaster_episodes] lists, and the [benecaster_player] title strip. Like the tenure chip, it is customised CSS class first, filter second:
| Want to | Reach for |
|---|---|
| Restyle it | CSS alone — the class list already carries the modifiers |
| Change the wording | benecaster_explicit_badge_label |
| Change which episodes get one | benecaster_explicit_badge_mode |
| Replace the markup | benecaster_explicit_badge_html |
The filters fire in the same cascade the tenure badge uses — mode → label → html, each handing its result to the next.
One thing differs, and it is worth knowing. The explicit badge carries a second class modifier naming the surface it is rendering on:
benecaster-explicit-badge--explicit benecaster-explicit-badge--context-card
--{mode} is clean, yes, or explicit. --context-{surface} is single, card, list, player, or shortcode. Because the same badge appears in five places with quite different space budgets, that second modifier lets one stylesheet show full text on the episode page and a compact marker in an archive grid — no filter, no template override.
→ Restyle or reword the Explicit badge — the three common asks, one filter each.
Checking Whether the Feature Is Active
Add-ons and theme templates can check the site-wide toggle without reading wp_options directly:
if ( \Benecaster\Membership\TenureBadgeAppender::is_enabled() ) {
// Tenure badge is on — safe to render tenure-specific UI.
}
See TenureBadgeAppender and TenureBadgeCalculator for the full class references.
See Also
Need this built rather than just documented? See our services →