www/apps/resources/app/commerce-modules/promotion/concepts/page.mdx
import { Table } from "docs-ui"
export const metadata = {
title: Promotion Concepts,
}
In this guide, you’ll learn about the main promotion and rule concepts in the Promotion Module.
<Note title="Looking for no-code docs?">Refer to this Medusa Admin User Guide to learn how to manage promotions using the dashboard.
</Note>A promotion, represented by the Promotion data model, is a discount that can be applied on cart items, shipping methods, or entire orders.
A promotion has two types:
standard: A standard promotion with rules.buyget: “A buy X get Y” promotion with rules. `standard` Promotion Examples
</Table.HeaderCell>
<Table.HeaderCell>
`buyget` Promotion Examples
</Table.HeaderCell>
</Table.Row>
</Table.Header> <Table.Body> <Table.Row> <Table.Cell>
A coupon code that gives customers 10% off their entire order.
</Table.Cell>
<Table.Cell>
Buy two shirts and get another for free.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
A coupon code that gives customers $15 off any shirt in their order.
</Table.Cell>
<Table.Cell>
Buy two shirts and get 10% off the entire order.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
A discount applied automatically for VIP customers that removes 10% off their shipping method’s amount.
</Table.Cell>
<Table.Cell>
Spend $100 and get free shipping.
</Table.Cell>
</Table.Row>
</Table.Body>
</Table> <Note>The Medusa Admin UI may not provide a way to create each of these promotion examples. However, they are supported by the Promotion Module and Medusa's workflows and API routes.
</Note>The limit property is available since Medusa v2.12.0.
A promotion can have usage limits to restrict how many times it can be used.
There are three ways to limit a promotion's usage:
limit property: This limits the total number of times the promotion can be used across all orders.All budgets are applied on the promotion. Once a budget is exhausted, the promotion can no longer be applied.
For example, if a promotion has a limit of 10 and its campaign has a global budget of $1000, the promotion can only be used 10 times or until the total discount given reaches $1000, whichever comes first.
A promotion can be restricted by a set of rules, each rule is represented by the PromotionRule data model.
For example, you can create a promotion that only customers of the VIP customer group can use.
A PromotionRule's attribute property indicates the property's name to which this rule is applied. For example, customer.groups.id.
The attribute must be an exact key that Medusa recognizes. Refer to Rule Attributes for the list of supported keys.
The expected value for the attribute is stored in the PromotionRuleValue data model. So, a rule can have multiple values.
When testing whether a promotion can be applied to a cart, the rule's attribute property and its values are tested on the cart itself.
For example, the cart's customer must be part of the customer group(s) indicated in the promotion rule's value.
The PromotionRule's operator property adds more flexibility to the rule’s condition rather than simple equality (eq).
For example, to restrict the promotion to only VIP and B2B customer groups:
PromotionRule record with its attribute property set to customer.groups.id and operator property to in.PromotionRuleValue records associated with the rule: one with the value VIP and the other B2B.In this case, a customer’s group must be in the VIP and B2B set of values to use the promotion.
A rule's attribute must be an exact key that Medusa recognizes for the resource the rule targets. The supported keys depend on where you set the rule:
rules property: Medusa tests the rule against the cart.target_rules and buy_rules properties: Medusa tests the rule against each cart line item or shipping method, based on the application method's target_type.Item-scoped attributes must be prefixed with items., and shipping-method-scoped attributes with shipping_methods.. For example, to target a product, use items.product.id, not product_id.
If you set an unrecognized attribute on an automatic promotion, Medusa filters the promotion out and never applies it, so the discount stays 0 without an error.
The following table lists the supported attributes for each rule scope:
<Table> <Table.Header> <Table.Row> <Table.HeaderCell> Rule Scope
</Table.HeaderCell>
<Table.HeaderCell>
Set On
</Table.HeaderCell>
<Table.HeaderCell>
Attributes
</Table.HeaderCell>
</Table.Row>
</Table.Header> <Table.Body> <Table.Row> <Table.Cell>
Cart
</Table.Cell>
<Table.Cell>
The promotion's `rules` property.
</Table.Cell>
<Table.Cell>
`customer.groups.id`, `region.id`, `shipping_address.country_code`, `sales_channel_id`, `currency_code`
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
Cart items
</Table.Cell>
<Table.Cell>
The application method's `target_rules` and `buy_rules` properties, when `target_type` is `items`.
</Table.Cell>
<Table.Cell>
`items.product.id`, `items.product.categories.id`, `items.product.collection_id`, `items.product.type_id`, `items.product.tags.id`
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
Shipping methods
</Table.Cell>
<Table.Cell>
The application method's `target_rules` and `buy_rules` properties, when `target_type` is `shipping_methods`.
</Table.Cell>
<Table.Cell>
`shipping_methods.shipping_option.shipping_option_type_id`
</Table.Cell>
</Table.Row>
</Table.Body>
</Table>To retrieve the supported attributes programmatically, use the listRuleAttributes JS SDK method or the List Rule Attribute Options Admin API route.
Along with the attributes in the table above, the cart context includes the cart's computed totals, such as item_subtotal, subtotal, item_total, and total. You can create a rule on one of these attributes using a numeric operator (gte, lte, gt, or lt) to restrict a promotion by order value, such as a minimum spend.
These numeric conditions aren't available in the Medusa Admin dashboard. Create them using workflows or the Admin API.
</Note>For example, to only apply a promotion when the cart's items subtotal is at least $100:
const { result } = await createPromotionsWorkflow(container)
.run({
input: {
promotionsData: [{
code: "SPEND100",
type: "standard",
status: "active",
application_method: {
type: "percentage",
target_type: "items",
allocation: "across",
value: 10,
currency_code: "usd",
},
rules: [
{
attribute: "item_subtotal",
operator: "gte",
values: ["100"],
},
],
}],
},
})
Medusa evaluates the rule against the cart's total when the promotion is applied. Within a single computation, the total doesn't reflect discounts from other promotions applied in the same computation.
If you're managing promotions using Medusa's workflows or the API routes that use them, you can specify rules for the promotion or its application method.
For example, if you're creating a promotion using the createPromotionsWorkflow:
const { result } = await createPromotionsWorkflow(container)
.run({
input: {
promotionsData: [{
code: "10OFF",
type: "standard",
status: "active",
application_method: {
type: "percentage",
target_type: "items",
allocation: "across",
value: 10,
currency_code: "usd",
},
rules: [
{
attribute: "customer.groups.id",
operator: "eq",
values: [
"cusgrp_123",
],
},
],
}],
},
})
In this example, the promotion is restricted to customers with the cusgrp_123 customer group.
For most use cases, it's recommended to use workflows instead of directly using the module's service.
</Note>If you're managing promotions using the Promotion Module's service, you can specify rules for the promotion or its application method in its methods.
For example, if you're creating a promotion with the createPromotions method:
const promotions = await promotionModuleService.createPromotions([
{
code: "50OFF",
type: "standard",
status: "active",
application_method: {
type: "percentage",
target_type: "items",
value: 50,
},
rules: [
{
attribute: "customer.group.id",
operator: "eq",
values: [
"cusgrp_123",
],
},
],
},
])
In this example, the promotion is restricted to customers with the cusgrp_123 customer group.
A promotion is applied on a resource if its attributes match the promotion's rules.
For example, consider you have the following promotion with a rule that restricts the promotion to a specific customer:
{
"code": "10OFF",
"type": "standard",
"status": "active",
"application_method": {
"type": "percentage",
"target_type": "items",
"allocation": "across",
"value": 10,
"currency_code": "usd"
},
"rules": [
{
"attribute": "customer_id",
"operator": "eq",
"values": [
"cus_123"
]
}
]
}
When you try to apply this promotion on a cart, the cart's customer_id is compared to the promotion rule's value based on the specified operator. So, the promotion will only be applied if the cart's customer_id is equal to cus_123.