All Tutorials

Create and manage selling plans

All Tutorials

Create and manage selling plans

Create and manage selling plans

Selling plans represent how products can be sold and purchased. This tutorial shows you how to create and manage selling plans in your app by illustrating two use cases: "Subscribe and save" and "Prepaid" subscriptions.

"Subscribe and save", also known as "pay per delivery", is a selling method where a customer pays for goods or services per delivery. "Prepaid" is a selling method where a customer makes a single payment upfront.

Requirements

Scopes

To use the GraphQL mutations, your app needs to request the following access scopes for a Shopify store:

  • read_customer_payment_methods: Allows an app to read customer payment methods.
  • read_own_subscription_contracts: Allows an app to read subscription contract mutations for contracts they own.
  • write_own_subscription_contracts: Allows an app to write subscription contract mutations for contracts they own.

Selling plans

A selling plan group represents a selling method. Selling plans are organized into selling plan groups. Each selling plan group includes products or variants as well as a delivery, billing, and pricing policy.

Selling plans objects diagram

View selling plans object descriptions
API object Description
Selling plan group Represents a selling method. For example, "Subscribe and save" or "Prepaid" subscriptions.
Selling plan Represents an alternative way to buy a product or variant. Selling plans are organized into a selling plan group. For example, individual selling plans might be named "Deliver weekly" or "Deliver monthly".
Policies Each selling plan is associated with the following policies:

  • Billing policy: The billing frequency associated with the subscription. For example, bill every week, or bill every three months.
  • Delivery policy: The delivery frequency associated with the subscription. For example, deliver every month, or deliver every other week.
  • Pricing policy: The type of pricing associated with the subscription. For example, 20% off, or a fixed $10 discount. The two types of pricing policies are:

    • Fixed pricing policy: A single pricing policy. For example, all billing cycles might have a flat 15% discount. An initial pricing policy is applied until a defined recurring policy kicks in.
    • Recurring pricing policy: A follow up pricing policy that applies if the initial pricing policy expires. For example, the first billing cycle might have a 20% discount and the recurring cycles after might be discounted by 15%.

Create a selling plan group

In the following example, a selling plan group is created using the sellingPlanGroupCreate mutation.

The input fields include name (the plan group name that customers see), merchantCode (the plan name that the merchant sees), sellingPlansToCreate (the individual selling plans to create), options (the selling plan options available in the drop-down list in the storefront), and position (how the options are sorted).

Associate a selling plan group to a product variant

Selling plans can be created with products or product variants already associated in the sellingPlanGroupCreate call.

However, you can also use the sellingPlanGroupAddProducts or sellingPlanGroupAddProductVariants mutation to make a separate call to associate a selling plan group with a product or product variant, without having to delete and recreate the selling plan group.

In the following example, the selling plan group is associated with a product variant.

Request

POST /admin/api/2021-01/graphql.json

mutation {
  sellingPlanGroupAddProductVariants(
    id: "gid://shopify/SellingPlanGroup/294969"
    productVariantIds: ["gid://shopify/ProductVariant/31374109802518"]
  ) {
    sellingPlanGroup {
      id
      productVariantCount
      productVariants(first: 10) {
        edges {
          node {
            id
            title
            inventoryQuantity
            product {
              id
              title
              totalInventory
            }
          }
        }
      }
    }
    userErrors {
      field
      message
    }
  }
}

View response

JSON response:

{
  "data": {
    "sellingPlanGroupAddProductVariants": {
      "sellingPlanGroup": {
        "id": "gid://shopify/SellingPlanGroup/294969",
        "productVariantCount": 1,
        "productVariants": {
          "edges": [
            {
              "node": {
                "id": "gid://shopify/ProductVariant/31374109802518",
                "title": "Type A",
                "inventoryQuantity": 7,
                "product": {
                  "id": "gid://shopify/Product/4352352387128",
                  "title": "The best coffee",
                  "totalInventory": 17
                }
              }
            }
          ]
        }
      },
      "userErrors": []
    }
  },
  "extensions": {
    "cost": {
      "requestedQueryCost": 32,
      "actualQueryCost": 14,
      "throttleStatus": {
        "maximumAvailable": 1000.0,
        "currentlyAvailable": 986,
        "restoreRate": 50.0
      }
    }
  }
}

Associate multiple selling plan groups to a product or product variant

If you want multiple selling plan groups to be associated with a product or product variant, you can use the productJoinSellingPlanGroups or productVariantJoinSellingPlanGroups mutations.

In the following example, two selling plan groups are associated with a product:

POST /admin/api/2021-01/graphql.json

mutation {
  productJoinSellingPlanGroups(
    id: "gid://shopify/Product/4353554710550"
    sellingPlanGroupIds: [
      "gid://shopify/SellingPlanGroup/294968"
      "gid://shopify/SellingPlanGroup/491542"
    ]
  ) {
    product {
      id
    }
    userErrors {
      field
      message
    }
  }
}

View response

JSON response:

{
  "data": {
    "productJoinSellingPlanGroups": {
      "product": {
        "id": "gid://shopify/Product/4353554710550"
      },
      "userErrors": []
    }
  },
  "extensions": {
    "cost": {
      "requestedQueryCost": 10,
      "actualQueryCost": 10,
      "throttleStatus": {
        "maximumAvailable": 1000.0,
        "currentlyAvailable": 990,
        "restoreRate": 50.0
      }
    }
  }
}

In the following example, two selling plan groups are associated with a product variant:

POST /admin/api/2021-01/graphql.json

mutation {
  productVariantJoinSellingPlanGroups(
    id: "gid://shopify/ProductVariant/31374835974166"
    sellingPlanGroupIds: [
      "gid://shopify/SellingPlanGroup/294968"
      "gid://shopify/SellingPlanGroup/491542"
    ]
  ) {
    productVariant {
      id
    }
    userErrors {
      field
      message
    }
  }
}

View response

JSON response:

{
  "data": {
    "productVariantJoinSellingPlanGroups": {
      "productVariant": {
        "id": "gid://shopify/ProductVariant/31374835974166"
      },
      "userErrors": []
    }
  },
  "extensions": {
    "cost": {
      "requestedQueryCost": 10,
      "actualQueryCost": 10,
      "throttleStatus": {
        "maximumAvailable": 1000.0,
        "currentlyAvailable": 990,
        "restoreRate": 50.0
      }
    }
  }
}

Configure a product to be sold only as a subscription

You can configure which products are sold on a subscription by specifying the requiresSellingPlan field as true in the productUpdate mutation. In the following example, the product is only sold on subscription.

Request

POST /admin/api/2021-01/graphql.json

mutation {
  productUpdate(
    input: { id: "gid://shopify/Product/1", requiresSellingPlan: true }
  ) {
    product {
      id
      requiresSellingPlan
    }
    userErrors {
      field
      message
    }
  }
}

View response

JSON response:

{
  "data": {
    "productUpdate": {
      "product": {
        "id": "gid://shopify/Product/1",
        "requiresSellingPlan": true
      },
      "userErrors": []
    }
  },
  "extensions": {
    "cost": {
      "requestedQueryCost": 10,
      "actualQueryCost": 10,
      "throttleStatus": {
        "maximumAvailable": 1000.0,
        "currentlyAvailable": 990,
        "restoreRate": 50.0
      }
    }
  }
}

Edit a selling plan group

You can call the sellingPlanGroupUpdate mutation to edit the attributes associated with a selling plan group. In the following example, the “Subscribe and save” selling plan group is renamed to “Pay per delivery”.

Request

POST /admin/api/2021-01/graphql.json

mutation {
  sellingPlanGroupUpdate(
    id: "gid://shopify/SellingPlanGroup/294969"
    input: { name: "Pay per delivery" }
  ) {
    sellingPlanGroup {
      id
      name
    }
    userErrors {
      field
      message
    }
  }
}

View response

JSON response:

{
  "data": {
    "sellingPlanGroupUpdate": {
      "sellingPlanGroup": {
        "id": "gid://shopify/SellingPlanGroup/294969",
        "name": "Pay per delivery"
      },
      "userErrors": []
    }
  },
  "extensions": {
    "cost": {
      "requestedQueryCost": 10,
      "actualQueryCost": 10,
      "throttleStatus": {
        "maximumAvailable": 1000.0,
        "currentlyAvailable": 990,
        "restoreRate": 50.0
      }
    }
  }
}

Remove a product or variant from a selling plan group

If you want to disassociate a product from a selling plan group, then you can use the sellingPlanGroupRemoveProducts mutation. To remove a product variant from a selling plan group, you can use the sellingPlanGroupRemoveProductVariants mutation. In this example, a product is removed from a selling plan group.

POST /admin/api/2021-01/graphql.json

mutation {
  sellingPlanGroupRemoveProducts(
    id: "gid://shopify/SellingPlanGroup/294968"
    productIds: ["gid://shopify/Product/4353554710550"]
  ) {
    userErrors {
      field
      message
    }
    removedProductIds
  }
}

View response

JSON response:

{
  "data": {
    "sellingPlanGroupRemoveProducts": {
      "userErrors": [],
      "removedProductIds": []
    }
  },
  "extensions": {
    "cost": {
      "requestedQueryCost": 10,
      "actualQueryCost": 10,
      "throttleStatus": {
        "maximumAvailable": 1000.0,
        "currentlyAvailable": 990,
        "restoreRate": 50.0
      }
    }
  }
}

Collecting payment information

Once you have created the selling plan group, customers can purchase a product on a subscription and add it to their cart. For more information on implementing storefront Liquid properties, refer to Showing selling plan groups and selling plans on a product page.

Customer payment methods allow merchants to initiate new charges, with or without the customer being present. Once a checkout is complete, a customer payment method is associated with the customer.

When an order includes subscription products, the payment method is associated with the subscription contract. This allows subscription apps to know which payment method to use for recurring payments.

Sell product as subscription only screenshot

Advanced delivery behaviors for subscriptions

If you need to define more advanced delivery behaviors for subscriptions, then you can use anchors. Anchors are useful in the following scenarios:

  • A merchant starts fulfillment on a specific date every month
  • A merchant wants to bill the 1st of every quarter
  • A customer expects their delivery every Tuesday

Anchors define the date when fulfillment is completed by a merchant or when delivery occurs for the customer for a given time cycle. You can also define a cutoff for which customers are eligible to enter this cycle and the desired behavior for customers who start their subscription inside the cutoff period.

To learn more about anchors, refer to the SellingPlanRecurringDeliveryPolicy reference topic.

View examples of intended results and anchor values
Intended result Values
Buy on anchor date, no cutoff (asap)
  • delivery_anchor: 15th of the month
  • delivery_anchor_intent: fulfillment
  • delivery_anchor_cutoff: 0
  • delivery_anchor_pre_cutoff_behavior: asap
  • first_billing_date: 01-15-2020
  • first_delivery: 01-15-2020
  • next_delivery: 02-15-2020
  • next_billing_date: 02-15-2020
Buy on anchor date, no cutoff (next)
  • delivery_anchor: 15th of the month
  • delivery_anchor_intent: fulfillment
  • delivery_anchor_cutoff: 0
  • delivery_anchor_pre_cutoff_behavior: next
  • first_billing_date: 01-15-2020
  • first_delivery: 01-15-2020
  • next_delivery: 02-15-2020
  • next_billing_date: 02-15-2020
Buy before anchor date, no cutoff (asap)
  • delivery_anchor: 15th of the month
  • delivery_anchor_intent: fulfillment
  • delivery_anchor_cutoff: 0
  • delivery_anchor_pre_cutoff_behavior: asap
  • first_billing_date: 01-09-2020
  • first_delivery: 01-09-2020
  • next_delivery: 01-15-2020
  • next_billing_date: 01-15-2020
Buy before anchor date, no cutoff (next)
  • delivery_anchor: 15th of the month
  • delivery_anchor_intent: fulfillment
  • delivery_anchor_cutoff: 0
  • delivery_anchor_pre_cutoff_behavior: next
  • first_billing_date: 01-09-2020
  • first_delivery: 01-15-2020
  • next_delivery: 02-15-2020
  • next_billing_date: 02-15-2020
Buy after anchor date, no cutoff (asap)
  • delivery_anchor: 15th of the month
  • delivery_anchor_intent: fulfillment
  • delivery_anchor_cutoff: 0
  • delivery_anchor_pre_cutoff_behavior: asap
  • first_billing_date: 01-24-2020
  • first_delivery: 01-24-2020
  • next_delivery: 02-15-2020
  • next_billing_date: 02-15-2020
Buy after anchor date, no cutoff (next)
  • delivery_anchor: 15th of the month
  • delivery_anchor_intent: fulfillment
  • delivery_anchor_cutoff: 0
  • delivery_anchor_pre_cutoff_behavior: next
  • first_billing_date: 01-24-2020
  • first_delivery: 02-15-2020
  • next_delivery: 03-15-2020
  • next_billing_date: 03-15-2020
Buy inside cutoff date, no cutoff (asap)
  • delivery_anchor: 15th of the month
  • delivery_anchor_intent: fulfillment
  • delivery_anchor_cutoff: 5
  • delivery_anchor_pre_cutoff_behavior: asap
  • first_billing_date: 01-12-2020
  • first_delivery: 01-15-2020
  • next_delivery: 02-15-2020
  • next_billing_date: 02-15-2020
Buy inside cutoff date, no cutoff (next)
  • delivery_anchor: 15th of the month
  • delivery_anchor_intent: fulfillment
  • delivery_anchor_cutoff: 5
  • delivery_anchor_pre_cutoff_behavior: next
  • first_billing_date: 01-12-2020
  • first_delivery: 02-15-2020
  • next_delivery: 03-15-2020
  • next_billing_date: 03-15-2020
Buy before cutoff date, no cutoff (asap)
  • delivery_anchor: 15th of the month
  • delivery_anchor_intent: fulfillment
  • delivery_anchor_cutoff: 5
  • delivery_anchor_pre_cutoff_behavior: asap
  • first_billing_date: 01-09-2020
  • first_delivery: 01-09-2020
  • next_delivery: 01-15-2020
  • next_billing_date: 01-15-2020
Buy before cutoff date, no cutoff (next)
  • delivery_anchor: 15th of the month
  • delivery_anchor_intent: fulfillment
  • delivery_anchor_cutoff: 5
  • delivery_anchor_pre_cutoff_behavior: next
  • first_billing_date: 01-09-2020
  • first_delivery: 01-15-2020
  • next_delivery: 02-15-2020
  • next_billing_date: 02-15-2020

Example call

The following example illustrates the use of anchors in an API call. In the example, the fulfillment and billing dates are defined as the 15th of each month.

POST /admin/api/2021-01/graphql.json

mutation {
  sellingPlanGroupCreate(
    input: {
      name: "Every month delivery"
      options: ["Delivery Every"]
      sellingPlansToCreate: {
        name: "15th of the month"
        options: ["15"]
        deliveryPolicy: {
          recurring: {
            anchors: { day: 15, type: MONTHDAY }
            preAnchorBehavior: ASAP
            intent: FULFILLMENT_BEGIN
            interval: MONTH
            intervalCount: 1
          }
        }
        pricingPolicies: {
          fixed: { adjustmentType: PERCENTAGE, adjustmentValue: { percentage: 10 } }
        }
        billingPolicy: {
          recurring: {
            interval: MONTH
            intervalCount: 1
            anchors: { day: 15, type: MONTHDAY }
          }
        }
      }
    }
    resources: { productVariantIds: ["gid://shopify/ProductVariant/1"] }
  ) {
    sellingPlanGroup {
      name
      sellingPlans(first: 10) {
        edges {
          node {
            name
            options
          }
        }
      }
    }
    userErrors {
      code
      field
      message
    }
  }
}

View response

JSON response:

{
  "data": {
    "sellingPlanGroupCreate": {
      "sellingPlanGroup": {
        "name": "Every month delivery",
        "sellingPlans": {
          "edges": [
            {
              "node": {
                "name": "15th of the month",
                "options": [
                  "15"
                ]
              }
            }
          ]
        }
      },
      "userErrors": []
    }
  },
  "extensions": {
    "cost": {
      "requestedQueryCost": 22,
      "actualQueryCost": 13,
      "throttleStatus": {
        "maximumAvailable": 1000.0,
        "currentlyAvailable": 987,
        "restoreRate": 50.0
      }
    }
  }
}

Next steps