Subscriptions
The Commerce extension renders subscription pricing from project subscription groups. A group holds one or more plans, each plan holds the features it includes and the billing frequencies it can be bought on, and helper tags render a whole group, a comparison table, or a single plan. Every subscription token and loop is populated only when the commerce extension is enabled.
Plan groups and loops
{each subscription_group[<id>].subscriptions as=sub} walks the plans inside one group. Replace <id> with the numeric id of the subscription group, and choose any alias for the current plan. Inside the loop, read the plan with {sub.*}; an alias such as plan works the same way, so {plan.name} and {sub.name} are the same field.
{each subscription_group[1].subscriptions as=sub}
<article class="plan">
<h3>{sub.name}</h3>
<p>{sub.description}</p>
<span>{sub.price}</span>
</article>
{/each}
The loop renders nothing when the group has no plans. Wrap it in {if ...} when an empty group should show a message instead of a blank section.
Plan fields
| Token | Meaning |
|---|---|
{sub.id} | Plan id. |
{sub.name} | Plan name. |
{sub.price} | Plan price. |
{sub.description} | Plan description. |
{sub.icon} | Plan icon URL, when set. |
{sub.icon_avif} | AVIF source for the plan icon, when available. |
{sub.is_popular} | True when the plan is marked popular. |
{sub.has_trial} | True when the plan offers a trial. |
{sub.is_current} | True when the signed-in customer is already on this plan. |
{sub.is_locked} | True when the plan cannot be selected. |
{sub.has_button_link} | True when the plan has a custom button link. |
{sub.button_text} | Label for the plan button. |
{sub.button_link_url} | Destination for the plan button. |
{sub.metadata.<key>} | Value stored under a plan metadata key. |
A plan can carry design metadata such as {sub.metadata.subtitle} or {sub.metadata.key}. Read a metadata value with its exact key.
{each subscription_group[1].subscriptions as=sub}
<article class="plan {if sub.is_popular}popular{/if}">
{if sub.icon}
<img src="{sub.icon}" alt="{sub.name}">
{/if}
<h3>{sub.name}</h3>
<p>{sub.description}</p>
{if sub.is_locked}
<button disabled>Unavailable</button>
{elseif sub.has_button_link}
<a href="{sub.button_link_url}">{sub.button_text}</a>
{/if}
</article>
{/each}
Billing frequencies
{each sub.frequencies as=freq} is nested inside the plan loop. Each plan exposes one or more billing frequencies, and each frequency carries its own price, interval, discount, and intro period.
| Token | Meaning |
|---|---|
{freq.price} | Price charged on this frequency. |
{freq.original_price} | Price before the discount. |
{freq.monthly_equivalent} | Price expressed per month, useful for longer cycles. |
{freq.interval_unit} | Billing unit, such as month, year, or week. |
{freq.interval_count} | Number of units between charges. |
{freq.is_monthly} | True when the frequency bills every month. |
{freq.is_yearly} | True when the frequency bills every year. |
{freq.has_discount} | True when a discount applies to the frequency. |
{freq.discount_amount} | Discount value. |
{freq.discount_type} | How the discount is expressed, such as percent or fixed. |
{freq.formatted_discount} | Discount ready to print. |
{freq.intro_duration} | Length of the intro or trial period. |
{freq.intro_unit} | Unit for the intro period. |
{each sub.frequencies as=freq}
{if freq.is_monthly}
<div class="price">
<span>{freq.price}</span>
<span>per {freq.interval_unit}</span>
</div>
{if freq.has_discount}
<span class="was">{freq.original_price}</span>
<span class="save">Save {freq.formatted_discount}</span>
{/if}
{if freq.intro_duration}
<span>{freq.intro_duration} {freq.intro_unit} free trial</span>
{/if}
{/if}
{if freq.is_yearly}
<div class="price">
<span>{freq.price}</span>
<span>per year, {freq.monthly_equivalent} per month</span>
</div>
{/if}
{/each}
Add limit=1 to show only the first frequency: {each sub.frequencies as=freq limit=1}.
Features
{each sub.features as=feature} is nested inside the plan loop. Each feature exposes its name.
| Token | Meaning |
|---|---|
{feature.name} | Feature name. |
<ul class="features">
{each sub.features as=feature limit=3}
<li>{feature.name}</li>
{/each}
</ul>
Helper tags
Three helper tags render prepared subscription layouts. Each takes the group id, and the single plan tag also takes the plan id.
| Tag | What it renders |
|---|---|
{subscription_group id=<id>} | Every plan in the group. |
{subscription_comparison group_id=<id>} | A feature comparison for the plans in the group. |
{subscription group_id=<id> id=<id>} ... {/subscription} | One plan, so its fields are available as {subscription.*}. |
{subscription_group id=1}
{subscription_comparison group_id=1}
The single plan tag scopes one plan and exposes the same fields under {subscription.*}, including the nested features and frequencies loops.
{subscription group_id=1 id=2}
<h1>{subscription.name}</h1>
<p>{subscription.description}</p>
<span>{subscription.price}</span>
{if subscription.has_button_link}
<a href="{subscription.button_link_url}">{subscription.button_text}</a>
{/if}
{each subscription.frequencies as=freq}
<li>{freq.price} per {freq.interval_unit}</li>
{/each}
{/subscription}
Group-level tokens
A group also exposes summary values. {subscription_group[<id>].max_discount} is the largest discount available in the group, and the upgrade and downgrade flags describe what the group allows.
| Token | Meaning |
|---|---|
{subscription_group[<id>].max_discount} | Largest discount available in the group. |
{subscription_group[<id>].upgrade_allowed} | True when plans in the group can be upgraded. |
{subscription_group[<id>].downgrade_allowed} | True when plans in the group can be downgraded. |
{subscription_group[<id>].allow_multiple_subscriptions} | True when a customer can hold more than one plan from the group. |
<span>Save up to {subscription_group[1].max_discount} with annual billing</span>

