Before a customer can subscribe, your product detail page needs to know which subscription frequencies are available, what discounts apply, and whether subscriptions are offered at all for that product or variant. The subscription offer endpoint gives you all of this in a single read, resolved against the plans and offers you have configured in Admin.
Fetching a subscription offer
Call GET /store/products/:id/subscription-offer to retrieve the effective offer for a product. Pass an optional variant_id query parameter to resolve the offer for a specific variant instead.
GET /store/products/:id/subscription-offer
GET /store/products/:id/subscription-offer?variant_id=variant_123
When variant_id is provided and that variant has its own offer configured, the variant-level offer takes precedence over the product-level offer. If no offer is found at either level, or if the offer is disabled, the response returns is_subscription_available: false.
Response shape
{
"subscription_offer": {
"is_subscription_available": true,
"product_id": "prod_123",
"variant_id": "variant_456",
"source_offer_id": "offer_789",
"source_scope": "variant",
"allowed_frequencies": [
{
"frequency_interval": "month",
"frequency_value": 1,
"label": "Monthly",
"discount": {
"type": "percentage",
"value": 10
}
},
{
"frequency_interval": "month",
"frequency_value": 3,
"label": "Every 3 months",
"discount": {
"type": "percentage",
"value": 15
}
}
],
"discount_semantics": "per_order",
"minimum_cycles": 2,
"trial": null
}
}
Response fields
| Field | Description |
|---|
is_subscription_available | true if subscriptions are available for this product or variant |
product_id | The product the offer applies to |
variant_id | The resolved variant, if variant_id was passed |
source_offer_id | The internal ID of the offer that was resolved |
source_scope | Whether the offer came from "variant" or "product" level |
allowed_frequencies | Array of available subscription cadences (see below) |
discount_semantics | How the discount is applied — for example "per_order" |
minimum_cycles | The minimum number of billing cycles before cancellation is allowed, or null |
trial | Trial configuration object, or null if no trial is configured |
Frequency items
Each object in allowed_frequencies describes one selectable cadence:
| Field | Description |
|---|
frequency_interval | Time unit: "week", "month", or "year" |
frequency_value | Number of intervals between billings |
label | Human-readable label you can display directly in your UI |
discount.type | Discount type — for example "percentage" or "fixed" |
discount.value | The discount amount in the given type’s unit |
Powering a PDP subscription selector
Use this endpoint to drive the subscription UI on your product detail page. A typical implementation fetches the offer on page load and uses the response to:
- Show or hide the subscription option — render the subscription selector only when
is_subscription_available is true.
- Populate the frequency selector — render one option per item in
allowed_frequencies, using label as the display text.
- Display the discount — show the savings for each frequency using
discount.type and discount.value.
- Set checkout metadata — when the customer confirms a frequency, use the selected
frequency_interval and frequency_value to set the line item metadata before calling the subscribe endpoint.
const { subscription_offer } = await medusa.client.fetch(
`/store/products/${productId}/subscription-offer?variant_id=${variantId}`
);
if (!subscription_offer.is_subscription_available) {
// Hide subscription option
return;
}
// Render a frequency option for each allowed cadence
const options = subscription_offer.allowed_frequencies.map((freq) => ({
label: freq.label,
interval: freq.frequency_interval,
value: freq.frequency_value,
discount: freq.discount,
}));
The allowed_frequencies array only contains cadences that are active in your Plans & Offers configuration. Use this list to validate which cadences a customer can select — do not allow customers to submit cadences that are not present in the response.