Represents a Discord subscription — a user's recurring access to one or more SKUs.
Do not hardcode the status integers
Discord renumbered this enum on 2026-06-16: INACTIVE and ENDING swapped places.
Code that compared status == 1 kept running and started meaning the opposite thing.
Use status/1, which returns :active, :inactive or :ending.
Status is not a permission
A subscription's status says what will happen at the end of the period, not what the user
may do right now. Discord is explicit that a subscription "can be ACTIVE outside its
current period or INACTIVE within its current period" — a failed payment keeps it active
while it retries, and a chargeback makes it inactive mid-period.
Entitlements decide access, not this. Read EDA.API.Entitlement for that question and
treat the status as billing information.
Summary
Functions
Returns true if the subscription has been cancelled.
Adds a change to an existing changeset for this entity.
Creates a changeset for batching mutations to this entity.
Returns true if the SKUs will change at renewal.
Fetches one subscription for a SKU. Returns a t/0.
Lists a SKU's subscriptions as structs.
Returns true if the subscription will renew at the end of the current period.
Names the subscription's status.
Returns the integer Discord uses for a status name, or nil for an unknown name.
Types
@type status() :: :active | :inactive | :ending | :unknown
A subscription status name.
@type t() :: %EDA.Subscription{ canceled_at: String.t() | nil, country: String.t() | nil, current_period_end: String.t() | nil, current_period_start: String.t() | nil, entitlement_ids: [String.t()] | nil, id: String.t() | nil, renewal_sku_ids: [String.t()] | nil, sku_ids: [String.t()] | nil, status: integer() | nil, user_id: String.t() | nil }
Functions
Returns true if the subscription has been cancelled.
Reads canceled_at, which is set as soon as the user cancels — the subscription is then
:ending until the period runs out, and :inactive after.
Examples
iex> EDA.Subscription.canceled?(%EDA.Subscription{canceled_at: "2026-09-19T12:00:00Z"})
true
iex> EDA.Subscription.canceled?(%EDA.Subscription{})
false
@spec change(EDA.Entity.Changeset.t(), atom(), term()) :: EDA.Entity.Changeset.t()
Adds a change to an existing changeset for this entity.
@spec changeset(t()) :: EDA.Entity.Changeset.t()
Creates a changeset for batching mutations to this entity.
Returns true if the SKUs will change at renewal.
renewal_sku_ids is nil while nothing is changing, and carries the new list once the
user has switched plan — an upgrade or downgrade that has not taken effect yet.
Examples
iex> EDA.Subscription.changing_plan?(%EDA.Subscription{sku_ids: ["1"], renewal_sku_ids: ["2"]})
true
iex> EDA.Subscription.changing_plan?(%EDA.Subscription{sku_ids: ["1"], renewal_sku_ids: nil})
false
@spec fetch_subscription(String.t() | integer(), String.t() | integer()) :: {:ok, t()} | {:error, term()}
Fetches one subscription for a SKU. Returns a t/0.
Named fetch_subscription/2 rather than fetch/2 because Access.fetch/2 owns that
arity.
Lists a SKU's subscriptions as structs.
Takes the same options as EDA.API.Subscription.list/2 — :user_id, :before,
:after, :limit.
Returns true if the subscription will renew at the end of the current period.
Only :active renews. :ending is still running but will stop, which is the distinction
worth acting on — it is the state to send a "your subscription ends on …" message about.
Examples
iex> EDA.Subscription.renewing?(%EDA.Subscription{status: 0})
true
iex> EDA.Subscription.renewing?(%EDA.Subscription{status: 2})
false
Names the subscription's status.
:active— running and scheduled to renew:inactive— not running and not being charged:ending— still running, but it will not renew
An integer Discord has since added comes back as :unknown rather than crashing a
case.
Examples
iex> EDA.Subscription.status(%EDA.Subscription{status: 0})
:active
iex> EDA.Subscription.status(%{"status" => 2})
:ending
iex> EDA.Subscription.status(%EDA.Subscription{status: 99})
:unknown
iex> EDA.Subscription.status(nil)
:unknown
Returns the integer Discord uses for a status name, or nil for an unknown name.
Useful for filtering a list without writing the numbers down.
Examples
iex> EDA.Subscription.status_value(:ending)
2
iex> EDA.Subscription.status_value(:nonsense)
nil