Grant a listener access to an episode or a bundle from an add-on
A grant is non-tier access: a named set of episodes somebody holds outside any membership tier. Core keeps the record and adds the slug to the listener’s feed; your add-on owns the commerce around it (prices, refunds, receipts). It takes two steps, and skipping the first is the mistake that gets made: put episodes behind the grant slug, then grant the slug to the buyer.
A grant slug with no availability rows resolves fine and carries nothing, so the buyer sees no change and there is no error to explain why. expires_at null means a lifetime grant, not an unknown one. Re-granting a slug the person already holds extends the existing row and never shortens it.
A grant is not a buy-up. A grant is owned outright, so it survives an over-limit demotion, a licence grace period and the end of the listener’s membership; a buy-up degrades with the membership it was bought on. Use a grant for a one-time purchase and a buy-up for a recurring upsell.
If your add-on already holds the purchase record and you would rather not duplicate it, answer the question through a filter instead: Sell episode access from your own add-on.
Code
<?php
// 1. Put episodes behind a grant slug. A slug is a NAMED set shared by every
// holder — a product or a bundle — never a per-person episode list, because
// the feed cache is keyed by slug. Availability rows are written exactly as
// they are for a tier.
( new \Benecaster\Availability\AvailabilityRepository() )->upsert(
$episode_id,
$show_id,
'season-one', // the grant slug
'2020-01-01 00:00:00' // available from (site-local; upsert() converts to UTC)
);
// 2. Grant it to the buyer. Keyed to the PERSON, so it survives a token reset,
// a cancellation and any change of subscriber type.
( new \Benecaster\Entitlement\EntitlementRepository() )->grant(
user_id: $user_id,
show_id: $show_id,
grant_slug: 'season-one',
expires_at: null, // null = lifetime
source: 'benecaster-addon-episode-sales', // your add-on's slug
external_ref: $order_id // your order or payment id
);
Need this built rather than just documented? See our services →