Subscriptions

Subscriptions are handled by gateways. A payment gateway must handle subscription plan support in order to establish a Commerce subscription for a user.

Because not all gateways support subscriptions via API, Commerce does not allow creating new subscription plans on the gateway. Support for this must be added by a plugin.

Once you’ve added a payment gateway that supports subscriptions, you can then navigate to CommerceSettingsSubscription Plans to set up subscription plans.

# Subscription Support Across Gateways

Currently, only the Commerce Stripe gateway (opens new window) supports subscriptions. If you need subscriptions for another gateway that supports them, a plugin first must be created that implements that gateway.

# Subscription Statuses

Commerce has the following subscription statuses:

  • active if a subscription is within a paid billing cycle, even if it’s set to cancel at the end of the current billing cycle.
  • canceled if a subscription is outside of a paid billing cycle and was canceled by the user.
  • expired if a subscription is outside of a paid billing cycle and was marked as such by the gateway, either because it exhausted a fixed number of billing cycles or a payment failed.
  • trial if a subscription is within the set amount of trial days from the beginning of the subscription.

In case more than one subscription status could be applied, the order of precedence is expired, canceled, trial and active.

# Subscribing

You create a subscription by subscribing a user to a subscription plan. As you are subscribing a user, it is possible to pass parameters for the subscription. All subscription gateways must support a trialDays parameter at minimum.

# Changing a Subscription’s Plan

Depending on the gateway, it might be possible to switch subscription plans. Please consult the gateway plugin’s documentation to see if it supports changing a subscription plan.

# Canceling a Subscription

Depending on the gateway, canceling subscriptions supports different options. Please consult the gateway plugin’s documentation to see if it supports canceling a subscription.

# Deleting Subscriptions or Plans

# Gateways

Not all gateways permit deleting subscription plans. Some gateways allow it and preserve all existing subscriptions, while others do not. Regardless of individual gateway support, we recommended against deleting a subscription plan on the gateway because it might result in the loss of historical data.

# Commerce

Commerce will attempt to delete all local subscription plans when a gateway is deleted. If any subscription—even an expired one—exists for a given plan, deletion will be prevented. Likewise, Commerce will prevent deleting a user that has active or expired subscriptions.

# Querying Subscriptions

You can fetch subscriptions in your templates or PHP code using subscription queries.

{# Create a new subscription query #}
{% set mySubscriptionQuery = craft.subscriptions() %}

Once you’ve created a subscription query, you can set parameters on it to narrow down the results, and then execute it by calling .all(). An array of Subscription (opens new window) objects will be returned.

See Element Queries in the Craft docs to learn about how element queries work.

# Example

We can display all of the current user’s subscriptions by doing the following:

  1. Create a subscription query with craft.subscriptions().
  2. Set the user parameter on it.
  3. Fetch the subscriptions with .all().
  4. Loop through the subscriptions using a for (opens new window) tag to output their HTML.
{# Make sure someone is logged in #}
{% requireLogin %}

{# Create a subscription query with the 'user' parameter #}
{% set mySubscriptionQuery = craft.subscriptions()
  .user(currentUser) %}

{# Fetch the subscriptions #}
{% set subscriptions = mySubscriptionQuery.all() %}

{# Display the subscriptions #}
{% for subscription in subscriptions %}
  <article>
    <h1><a href="{{ subscription.url }}">{{ subscription.title }}</a></h1>
    {{ subscription.summary }}
    <a href="{{ subscription.url }}">Learn more</a>
  </article>
{% endfor %}

# Parameters

Subscription queries support the following parameters:

Param Description
afterPopulate Performs any post-population processing on elements.
andRelatedTo Narrows the query results to only subscriptions that are related to certain other elements.
asArray Causes the query to return matching subscriptions as arrays of data, rather than Subscription (opens new window) objects.
cache Enables query cache for this Query.
clearCachedResult Clears the cached result (opens new window).
dateCanceled Narrows the query results based on the subscriptions’ cancellation date.
dateCreated Narrows the query results based on the subscriptions’ creation dates.
dateExpired Narrows the query results based on the subscriptions’ expiration date.
dateSuspended Narrows the query results based on the subscriptions’ suspension date.
dateUpdated Narrows the query results based on the subscriptions’ last-updated dates.
eagerly Causes the query to be used to eager-load results for the query’s source element and any other elements in its collection.
fixedOrder Causes the query results to be returned in the order specified by id.
gatewayId Narrows the query results based on the gateway, per its ID.
hasStarted Narrows the query results to only subscriptions that have started.
id Narrows the query results based on the subscriptions’ IDs.
ignorePlaceholders Causes the query to return matching subscriptions as they are stored in the database, ignoring matching placeholder elements that were set by craft\services\Elements::setPlaceholderElement() (opens new window).
inBulkOp Narrows the query results to only subscriptions that were involved in a bulk element operation.
inReverse Causes the query results to be returned in reverse order.
isCanceled Narrows the query results to only subscriptions that are canceled.
isExpired Narrows the query results to only subscriptions that have expired.
isSuspended Narrows the query results to only subscriptions that are suspended.
language Determines which site(s) the subscriptions should be queried in, based on their language.
limit Determines the number of subscriptions that should be returned.
nextPaymentDate Narrows the query results based on the subscriptions’ next payment dates.
offset Determines how many subscriptions should be skipped in the results.
onTrial Narrows the query results to only subscriptions that are on trial.
orderBy Determines the order that the subscriptions should be returned in. (If empty, defaults to dateCreated DESC.)
orderId Narrows the query results based on the order, per its ID.
plan Narrows the query results based on the subscription plan.
planId Narrows the query results based on the subscription plans’ IDs.
preferSites If unique() (opens new window) is set, this determines which site should be selected when querying multi-site elements.
prepForEagerLoading Prepares the query for lazy eager loading.
prepareSubquery Prepares the element query and returns its subquery (which determines what elements will be returned).
reference Narrows the query results based on the reference.
relatedTo Narrows the query results to only subscriptions that are related to certain other elements.
render Executes the query and renders the resulting elements using their partial templates.
search Narrows the query results to only subscriptions that match a search query.
siteSettingsId Narrows the query results based on the subscriptions’ IDs in the elements_sites table.
status Narrows the query results based on the subscriptions’ statuses.
trashed Narrows the query results to only subscriptions that have been soft-deleted.
trialDays Narrows the query results based on the number of trial days.
uid Narrows the query results based on the subscriptions’ UIDs.
user Narrows the query results based on the subscriptions’ user accounts.
userId Narrows the query results based on the subscriptions’ user accounts’ IDs.
wasCountEagerLoaded Returns whether the query result count was already eager loaded by the query's source element.
wasEagerLoaded Returns whether the query results were already eager loaded by the query's source element.
with Causes the query to return matching subscriptions eager-loaded with related elements.
withCustomFields Sets whether custom fields should be factored into the query.

# afterPopulate

Performs any post-population processing on elements.

# andRelatedTo

Narrows the query results to only subscriptions that are related to certain other elements.

See Relations (opens new window) for a full explanation of how to work with this parameter.

{# Fetch all subscriptions that are related to myCategoryA and myCategoryB #}
{% set subscriptions = craft.subscriptions()
  .relatedTo(myCategoryA)
  .andRelatedTo(myCategoryB)
  .all() %}

# asArray

Causes the query to return matching subscriptions as arrays of data, rather than Subscription (opens new window) objects.

{# Fetch subscriptions as arrays #}
{% set subscriptions = craft.subscriptions()
  .asArray()
  .all() %}

# cache

Enables query cache for this Query.

# clearCachedResult

Clears the cached result (opens new window).

# dateCanceled

Narrows the query results based on the subscriptions’ cancellation date.

Possible values include:

Value Fetches subscriptions…
'>= 2018-04-01' that were canceled on or after 2018-04-01.
'< 2018-05-01' that were canceled before 2018-05-01
['and', '>= 2018-04-04', '< 2018-05-01'] that were canceled between 2018-04-01 and 2018-05-01.
{# Fetch subscriptions that were canceled recently #}
{% set aWeekAgo = date('7 days ago')|atom %}

{% set subscriptions = craft.subscriptions()
  .dateCanceled(">= #{aWeekAgo}")
  .all() %}

# dateCreated

Narrows the query results based on the subscriptions’ creation dates.

Possible values include:

Value Fetches subscriptions…
'>= 2018-04-01' that were created on or after 2018-04-01.
'< 2018-05-01' that were created before 2018-05-01.
['and', '>= 2018-04-04', '< 2018-05-01'] that were created between 2018-04-01 and 2018-05-01.
now/today/tomorrow/yesterday that were created at midnight of the specified relative date.
{# Fetch subscriptions created last month #}
{% set start = date('first day of last month')|atom %}
{% set end = date('first day of this month')|atom %}

{% set subscriptions = craft.subscriptions()
  .dateCreated(['and', ">= #{start}", "< #{end}"])
  .all() %}

# dateExpired

Narrows the query results based on the subscriptions’ expiration date.

Possible values include:

Value Fetches subscriptions…
'>= 2018-04-01' that expired on or after 2018-04-01.
'< 2018-05-01' that expired before 2018-05-01
['and', '>= 2018-04-04', '< 2018-05-01'] that expired between 2018-04-01 and 2018-05-01.
{# Fetch subscriptions that expired recently #}
{% set aWeekAgo = date('7 days ago')|atom %}

{% set subscriptions = craft.subscriptions()
  .dateExpired(">= #{aWeekAgo}")
  .all() %}

# dateSuspended

Narrows the query results based on the subscriptions’ suspension date.

Possible values include:

Value Fetches subscriptions…
'>= 2018-04-01' that were suspended on or after 2018-04-01.
'< 2018-05-01' that were suspended before 2018-05-01
['and', '>= 2018-04-04', '< 2018-05-01'] that were suspended between 2018-04-01 and 2018-05-01.
{# Fetch subscriptions that were suspended recently #}
{% set aWeekAgo = date('7 days ago')|atom %}

{% set subscriptions = craft.subscriptions()
  .dateSuspended(">= #{aWeekAgo}")
  .all() %}

# dateUpdated

Narrows the query results based on the subscriptions’ last-updated dates.

Possible values include:

Value Fetches subscriptions…
'>= 2018-04-01' that were updated on or after 2018-04-01.
'< 2018-05-01' that were updated before 2018-05-01.
['and', '>= 2018-04-04', '< 2018-05-01'] that were updated between 2018-04-01 and 2018-05-01.
now/today/tomorrow/yesterday that were updated at midnight of the specified relative date.
{# Fetch subscriptions updated in the last week #}
{% set lastWeek = date('1 week ago')|atom %}

{% set subscriptions = craft.subscriptions()
  .dateUpdated(">= #{lastWeek}")
  .all() %}

# eagerly

Causes the query to be used to eager-load results for the query’s source element and any other elements in its collection.

# fixedOrder

Causes the query results to be returned in the order specified by id.

If no IDs were passed to id, setting this to true will result in an empty result set.

{# Fetch subscriptions in a specific order #}
{% set subscriptions = craft.subscriptions()
  .id([1, 2, 3, 4, 5])
  .fixedOrder()
  .all() %}

# gatewayId

Narrows the query results based on the gateway, per its ID.

Possible values include:

Value Fetches subscriptions…
1 with a gateway with an ID of 1.
'not 1' not with a gateway with an ID of 1.
[1, 2] with a gateway with an ID of 1 or 2.
['not', 1, 2] not with a gateway with an ID of 1 or 2.

# hasStarted

Narrows the query results to only subscriptions that have started.

{# Fetch started subscriptions #}
{% set subscriptions = craft.subscriptions()
  .hasStarted()
  .all() %}

# id

Narrows the query results based on the subscriptions’ IDs.

Possible values include:

Value Fetches subscriptions…
1 with an ID of 1.
'not 1' not with an ID of 1.
[1, 2] with an ID of 1 or 2.
['not', 1, 2] not with an ID of 1 or 2.
{# Fetch the subscription by its ID #}
{% set subscription = craft.subscriptions()
  .id(1)
  .one() %}

This can be combined with fixedOrder if you want the results to be returned in a specific order.

# ignorePlaceholders

Causes the query to return matching subscriptions as they are stored in the database, ignoring matching placeholder elements that were set by craft\services\Elements::setPlaceholderElement() (opens new window).

# inBulkOp

Narrows the query results to only subscriptions that were involved in a bulk element operation.

# inReverse

Causes the query results to be returned in reverse order.

{# Fetch subscriptions in reverse #}
{% set subscriptions = craft.subscriptions()
  .inReverse()
  .all() %}

# isCanceled

Narrows the query results to only subscriptions that are canceled.

{# Fetch canceled subscriptions #}
{% set subscriptions = craft.subscriptions()
  .isCanceled()
  .all() %}

# isExpired

Narrows the query results to only subscriptions that have expired.

{# Fetch expired subscriptions #}
{% set subscriptions = craft.subscriptions()
  .isExpired()
  .all() %}

# isSuspended

Narrows the query results to only subscriptions that are suspended.

{# Fetch suspended subscriptions #}
{% set subscriptions = craft.subscriptions()
  .isSuspended()
  .all() %}

# language

Determines which site(s) the subscriptions should be queried in, based on their language.

Possible values include:

Value Fetches subscriptions…
'en' from sites with a language of en.
['en-GB', 'en-US'] from sites with a language of en-GB or en-US.
['not', 'en-GB', 'en-US'] not in sites with a language of en-GB or en-US.

Elements that belong to multiple sites will be returned multiple times by default. If you only want unique elements to be returned, use unique() (opens new window) in conjunction with this.

{# Fetch subscriptions from English sites #}
{% set subscriptions = craft.subscriptions()
  .language('en')
  .all() %}

# limit

Determines the number of subscriptions that should be returned.

{# Fetch up to 10 subscriptions  #}
{% set subscriptions = craft.subscriptions()
  .limit(10)
  .all() %}

# nextPaymentDate

Narrows the query results based on the subscriptions’ next payment dates.

Possible values include:

Value Fetches subscriptions…
'>= 2018-04-01' with a next payment on or after 2018-04-01.
'< 2018-05-01' with a next payment before 2018-05-01
['and', '>= 2018-04-04', '< 2018-05-01'] with a next payment between 2018-04-01 and 2018-05-01.
{# Fetch subscriptions with a payment due soon #}
{% set aWeekFromNow = date('+7 days')|atom %}

{% set subscriptions = craft.subscriptions()
  .nextPaymentDate("< #{aWeekFromNow}")
  .all() %}

# offset

Determines how many subscriptions should be skipped in the results.

{# Fetch all subscriptions except for the first 3 #}
{% set subscriptions = craft.subscriptions()
  .offset(3)
  .all() %}

# onTrial

Narrows the query results to only subscriptions that are on trial.

{# Fetch trialed subscriptions #}
{% set subscriptions = craft.subscriptions()
  .onTrial()
  .all() %}

# orderBy

Determines the order that the subscriptions should be returned in. (If empty, defaults to dateCreated DESC.)

{# Fetch all subscriptions in order of date created #}
{% set subscriptions = craft.subscriptions()
  .orderBy('dateCreated ASC')
  .all() %}

# orderId

Narrows the query results based on the order, per its ID.

Possible values include:

Value Fetches subscriptions…
1 with an order with an ID of 1.
'not 1' not with an order with an ID of 1.
[1, 2] with an order with an ID of 1 or 2.
['not', 1, 2] not with an order with an ID of 1 or 2.

# plan

Narrows the query results based on the subscription plan.

Possible values include:

Value Fetches subscriptions…
'foo' for a plan with a handle of foo.
['foo', 'bar'] for plans with a handle of foo or bar.
a Plan (opens new window) object for a plan represented by the object.
{# Fetch Supporter plan subscriptions #}
{% set subscriptions = craft.subscriptions()
  .plan('supporter')
  .all() %}

# planId

Narrows the query results based on the subscription plans’ IDs.

Possible values include:

Value Fetches subscriptions…
1 for a plan with an ID of 1.
[1, 2] for plans with an ID of 1 or 2.
['not', 1, 2] for plans not with an ID of 1 or 2.

# preferSites

If unique() (opens new window) is set, this determines which site should be selected when querying multi-site elements.

For example, if element “Foo” exists in Site A and Site B, and element “Bar” exists in Site B and Site C, and this is set to ['c', 'b', 'a'], then Foo will be returned for Site B, and Bar will be returned for Site C.

If this isn’t set, then preference goes to the current site.

{# Fetch unique subscriptions from Site A, or Site B if they don’t exist in Site A #}
{% set subscriptions = craft.subscriptions()
  .site('*')
  .unique()
  .preferSites(['a', 'b'])
  .all() %}

# prepForEagerLoading

Prepares the query for lazy eager loading.

# prepareSubquery

Prepares the element query and returns its subquery (which determines what elements will be returned).

# reference

Narrows the query results based on the reference.

# relatedTo

Narrows the query results to only subscriptions that are related to certain other elements.

See Relations (opens new window) for a full explanation of how to work with this parameter.

{# Fetch all subscriptions that are related to myCategory #}
{% set subscriptions = craft.subscriptions()
  .relatedTo(myCategory)
  .all() %}

# render

Executes the query and renders the resulting elements using their partial templates.

If no partial template exists for an element, its string representation will be output instead.

Narrows the query results to only subscriptions that match a search query.

See Searching (opens new window) for a full explanation of how to work with this parameter.

{# Get the search query from the 'q' query string param #}
{% set searchQuery = craft.app.request.getQueryParam('q') %}

{# Fetch all subscriptions that match the search query #}
{% set subscriptions = craft.subscriptions()
  .search(searchQuery)
  .all() %}

# siteSettingsId

Narrows the query results based on the subscriptions’ IDs in the elements_sites table.

Possible values include:

Value Fetches subscriptions…
1 with an elements_sites ID of 1.
'not 1' not with an elements_sites ID of 1.
[1, 2] with an elements_sites ID of 1 or 2.
['not', 1, 2] not with an elements_sites ID of 1 or 2.
{# Fetch the subscription by its ID in the elements_sites table #}
{% set subscription = craft.subscriptions()
  .siteSettingsId(1)
  .one() %}

# status

Narrows the query results based on the subscriptions’ statuses.

Possible values include:

Value Fetches subscriptions…
'active' (default) that are active.
'expired' that have expired.
{# Fetch expired subscriptions #}
{% set subscriptions = craft.subscriptions()
  .status('expired')
  .all() %}

# trashed

Narrows the query results to only subscriptions that have been soft-deleted.

{# Fetch trashed subscriptions #}
{% set subscriptions = craft.subscriptions()
  .trashed()
  .all() %}

# trialDays

Narrows the query results based on the number of trial days.

# uid

Narrows the query results based on the subscriptions’ UIDs.

{# Fetch the subscription by its UID #}
{% set subscription = craft.subscriptions()
  .uid('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx')
  .one() %}

# user

Narrows the query results based on the subscriptions’ user accounts.

Possible values include:

Value Fetches subscriptions…
'foo' for a user account with a username of foo
['foo', 'bar'] for user accounts with a username of foo or bar.
a User (opens new window) object for a user account represented by the object.
{# Fetch the current user's subscriptions #}
{% set subscriptions = craft.subscriptions()
  .user(currentUser)
  .all() %}

# userId

Narrows the query results based on the subscriptions’ user accounts’ IDs.

Possible values include:

Value Fetches subscriptions…
1 for a user account with an ID of 1.
[1, 2] for user accounts with an ID of 1 or 2.
['not', 1, 2] for user accounts not with an ID of 1 or 2.
{# Fetch the current user's subscriptions #}
{% set subscriptions = craft.subscriptions()
  .userId(currentUser.id)
  .all() %}

# wasCountEagerLoaded

Returns whether the query result count was already eager loaded by the query's source element.

# wasEagerLoaded

Returns whether the query results were already eager loaded by the query's source element.

# with

Causes the query to return matching subscriptions eager-loaded with related elements.

See Eager-Loading Elements (opens new window) for a full explanation of how to work with this parameter.

{# Fetch subscriptions eager-loaded with the "Related" field’s relations #}
{% set subscriptions = craft.subscriptions()
  .with(['related'])
  .all() %}

# withCustomFields

Sets whether custom fields should be factored into the query.

Parts of this page are generated or assembled by automations. While we greatly appreciate contributions to the documentation, reporting automated content issues will allow us to fix them at the source!