Skip to main content

Update a subscription contract

Subscription contracts often require updates, such as when a customer needs to update their payment method or requests a change to their subscription.

This guide shows you how to submit the desired changes to subscriptionContractUpdateCalculate, review Shopify's calculated result, and commit a new active version of the contract. Fields that you omit remain unchanged.

Available in the 2026-10 release candidate

The SubscriptionContractCalculation API is available in the GraphQL Admin API 2026-10 release candidate. If your app uses the SubscriptionDraft API, refer to the legacy contract update guide.


Note
  • Most subscriptions, pre-order and try before you buy apps need to request API access through the Partner Dashboard. We give API access to apps that are designed according to our principles for subscriptions, pre-order and TBYB apps.
  • Public apps that use subscriptions, pre-order or TBYB need to meet specific requirements to be published on the Shopify App Store.
  • Custom apps created in the Shopify admin can't use subscriptions, pre-order or TBYB because these apps can't use extensions or request access to protected scopes. If you're building a solution for a single store, then build your custom app in the Partner Dashboard.

Anchor to How contract calculation worksHow contract calculation works

Contract updates use an asynchronous calculate and commit flow:

  1. Query the current contract state.
  2. Submit the fields that you want to change.
  3. Poll the calculation or wait for a webhook. Shopify emits subscription_contract_calculations/succeed when a calculation succeeds and subscription_contract_calculations/fail when it fails or is voided.
  4. Review the complete calculated contract, projected totals, warnings, or errors.
  5. Commit a successful calculation.

Committing creates a new version of the contract. It doesn't modify orders or fulfillments that have already been created. Use orderEditBegin to edit an existing order.


Anchor to Step 1: Query the subscription contractStep 1: Query the subscription contract

Query the contract before calculating an update. Include the IDs and complete values for any collection that you plan to replace, such as lines or manual discounts. When you rebuild the lines array, a line's variantId is the value that you pass as productVariantId in the input:

POST https://{shop}.myshopify.com/admin/api/2026-10/graphql.json

GraphQL query

query GetSubscriptionContract($id: ID!) {
subscriptionContract(id: $id) {
id
status
note
lines(first: 50) {
nodes {
id
quantity
variantId
currentPrice {
amount
currencyCode
}
customAttributes {
key
value
}
}
}
billingPolicy {
interval
intervalCount
}
deliveryPolicy {
interval
intervalCount
}
deliveryMethod {
__typename
}
customerPaymentMethod {
id
}
}
}

Variables

{
"id": "gid://shopify/SubscriptionContract/123"
}

Anchor to Step 2: Calculate the updateStep 2: Calculate the update

Call subscriptionContractUpdateCalculate with the contract ID and desired changes. The following example changes an existing line's quantity and applies a discount code. Because lines replaces the entire collection, this example assumes a single-line contract. For a contract with multiple lines, include every line you want to keep:

POST https://{shop}.myshopify.com/admin/api/2026-10/graphql.json

GraphQL mutation

mutation UpdateSubscriptionContract {
subscriptionContractUpdateCalculate(
contractId: "gid://shopify/SubscriptionContract/123"
contractUpdateInput: {
withMerchandiseCustomizations: true
lines: [
{
productVariantLine: {
id: "gid://shopify/SubscriptionLine/existing-line-uuid"
productVariantId: "gid://shopify/ProductVariant/111"
quantity: 2
customAttributes: []
discounts: []
}
}
]
discountCodes: [{ redeemCode: "SAVE10" }]
}
) {
subscriptionContractCalculation {
... on SubscriptionContractCalculationPending {
id
}
}
userErrors {
field
message
code
}
}
}

JSON response

{
"data": {
"subscriptionContractUpdateCalculate": {
"subscriptionContractCalculation": {
"id": "gid://shopify/SubscriptionContractCalculation/789"
},
"userErrors": []
}
}
}

If the mutation returns a user error, then correct the input before polling. Shopify doesn't create an asynchronous calculation when input validation fails.

Collections replace existing values

When you provide lines or manualDiscounts, the array replaces the complete existing collection. Include the IDs and values for every existing item that you want to keep. Omit an existing item to remove it, and omit id from a new item.

Providing a line also replaces that line's customAttributes and discounts, and you must state both whenever you include the line. Pass the line's existing customAttributes (queried in Step 1) and re-declare any line-level discounts to keep them, or pass an empty list to clear either.


Anchor to Step 3: Poll for the calculation resultStep 3: Poll for the calculation result

The calculate mutation returns a pending calculation. Poll for the result and review it before committing.

Contract calculations run asynchronously. Most calculations finish in less than three seconds, but Functions and external services can increase processing time. Use the calculation ID returned by the calculate mutation to query subscriptionContractCalculation:

POST https://{shop}.myshopify.com/admin/api/2026-10/graphql.json

GraphQL query

query PollSubscriptionContractCalculation($id: ID!) {
subscriptionContractCalculation(id: $id) {
__typename
... on SubscriptionContractCalculationPending {
id
}
... on SubscriptionContractCalculationSuccess {
id
calculatedContract {
id
lines(first: 10) {
nodes {
id
quantity
title
variantId
}
}
}
warnings {
code
message
}
projectedOrderTotals {
subtotal {
amount
currencyCode
}
totalDelivery {
amount
currencyCode
}
totalTax {
amount
currencyCode
}
totalMerchandiseDiscounts {
amount
currencyCode
}
totalDeliveryDiscounts {
amount
currencyCode
}
total {
amount
currencyCode
}
}
}
... on SubscriptionContractCalculationFailure {
id
errors {
code
message
}
}
}
}

Variables

{
"id": "gid://shopify/SubscriptionContractCalculation/789"
}

Pending response

{
"data": {
"subscriptionContractCalculation": {
"__typename": "SubscriptionContractCalculationPending",
"id": "gid://shopify/SubscriptionContractCalculation/789"
}
}
}

The query returns one of the following types:

TypeMeaningAction
SubscriptionContractCalculationPendingShopify initiated or is processing the calculation.Continue polling.
SubscriptionContractCalculationSuccessThe calculated contract is ready to review and commit.Review the result before committing.
SubscriptionContractCalculationFailureThe calculation failed (processed with errors) or Shopify voided it (never processed, for example, because of an infrastructure issue).If it failed, read errors and correct the input before calculating again. If it was voided, errors is empty and there's nothing to fix in the input, so retry the calculation.

Wait two seconds before the first poll, poll every second, and stop after 30 seconds. If the calculation is still pending, then retry later or wait for a webhook instead of increasing the polling frequency.

Anchor to Review the calculated contractReview the calculated contract

Before committing, inspect the following fields on SubscriptionContractCalculationSuccess:

  • calculatedContract: The complete contract snapshot that the commit applies.
  • projectedOrderTotals: The projected merchandise, delivery, discount, tax, and total amounts.
  • warnings: Existing-data or compatibility problems that don't prevent the calculation from succeeding.

A successful calculation is immutable. If you need to change the input, then start a new calculation and commit only the result that you want to make active.

Review warnings

A successful calculation can contain warnings. Review them before committing because they can identify disabled currencies, delivery configuration problems, or other existing contract data that Shopify preserved instead of blocking the update.

Anchor to Use webhooks instead of pollingUse webhooks instead of polling

For event-driven processing, subscribe to the following webhook topics:

TopicDescription
subscription_contract_calculations/succeedThe calculation succeeded and is ready to review and commit.
subscription_contract_calculations/failThe calculation failed or Shopify voided it. A failed calculation has errors to query; a voided one has empty errors, so retry it.

Create a webhook subscription with webhookSubscriptionCreate:

POST https://{shop}.myshopify.com/admin/api/2026-10/graphql.json

GraphQL mutation

mutation CreateCalculationWebhook {
webhookSubscriptionCreate(
topic: SUBSCRIPTION_CONTRACT_CALCULATIONS_SUCCEED
webhookSubscription: {
callbackUrl: "https://example.com/webhooks/calculation-success"
format: JSON
}
) {
webhookSubscription {
id
}
userErrors {
field
message
}
}
}

Webhook payload

{
"id": 789,
"admin_graphql_api_id": "gid://shopify/SubscriptionContractCalculation/789",
"state": "succeeded"
}

Use admin_graphql_api_id to query the completed calculation. Webhook delivery doesn't commit the result automatically.


Anchor to Step 4: Commit the calculationStep 4: Commit the calculation

After the calculation succeeds and you've reviewed the result, call subscriptionContractCalculationCommit. The commit makes the calculated contract active for billing:

POST https://{shop}.myshopify.com/admin/api/2026-10/graphql.json

GraphQL mutation

mutation CommitSubscriptionContractCalculation {
subscriptionContractCalculationCommit(
id: "gid://shopify/SubscriptionContractCalculation/789"
) {
contract {
... on SubscriptionContract {
id
status
}
}
userErrors {
field
message
code
}
}
}

JSON response

{
"data": {
"subscriptionContractCalculationCommit": {
"contract": {
"id": "gid://shopify/SubscriptionContract/999",
"status": "ACTIVE"
},
"userErrors": []
}
}
}

Handle commit user errors by code:

CodeMeaningAction
NOT_READY_TO_COMMITThe calculation hasn't succeeded yet.Continue polling before retrying the commit.
CALCULATION_NOT_FOUNDThe calculation ID doesn't exist or isn't available to the app.Verify the ID and app access.
STALE_CONTRACTThe contract changed after this calculation started.Query the latest contract state and calculate again.
INVALIDShopify can't commit the calculation.Use field and message to correct the request.

You can safely retry polling and commit requests. Retrying a calculate mutation creates a new calculation, so retain the ID for the result that you intend to commit. Retrying a commit for an already committed calculation returns the committed result.


Anchor to Update common contract attributesUpdate common contract attributes

Use the following update semantics when preparing contractUpdateInput:

InputBehavior
withMerchandiseCustomizationsRequired. Set to true to run merchandise transformations, both fixed bundle expansion from a stored definition and Shopify Functions that customize merchandise, such as cart transforms. Set to false to bypass all merchandise transformations.
linesReplaces all lines. Include an id to keep or update an existing line. Omit id to add a line. Omit the field to leave all lines unchanged.
manualDiscountsReplaces all manual order and delivery discounts. Pass [] to remove all manual discounts.
discountCodesApplies codes during this calculation. Shopify resolves them into manual discounts on the contract instead of storing the codes.
billingPolicyReplaces the billing cadence and anchors.
deliveryPolicyReplaces the delivery anchors and optional multi-fulfillment configuration.
deliveryMethodReplaces the delivery method. Pass none to remove it, or fetchAvailableDeliveryOptions to discover options without changing the committed method.
paymentMethodReplaces the payment method. Pass none to remove it.
noteReplaces the note. Pass null to clear it.
customAttributesReplaces all contract attributes. Pass [] to remove them.
appManagedBillingConfigReplaces app-managed minCycles and maxCycles metadata. These are stored on the contract but don't affect calculated billing cycle dates.

Manage contract status separately with subscriptionContractActivate, subscriptionContractPause, subscriptionContractCancel, subscriptionContractExpire, or subscriptionContractFail.

Anchor to Discover delivery optionsDiscover delivery options

To retrieve shipping options for a new address without changing the committed delivery method, provide fetchAvailableDeliveryOptions:

deliveryMethod: {
fetchAvailableDeliveryOptions: {
address: {
lastName: "Ishida"
address1: "123 Main St"
city: "Toronto"
provinceCode: "ON"
countryCode: CA
zip: "M5V 1A1"
}
withDeliveryCustomizations: true
}
}

When you poll the calculation in Step 3, read the results from the deliveryOptions field on SubscriptionContractCalculationSuccess, a union of shipping, local delivery, and pickup options. Then start a new calculation with the selected shipping or local delivery option. Delivery customization Functions run only when withDeliveryCustomizations is true.



Was this page helpful?