Behavior of multiple anchors in subscription contract

Context

We are working with subscription contracts to create anchored subscriptions. For example, have a subscription always bill on the 10th and the 20th of a month - regardless of when it was created.

Selling plan setup

  • Single selling plan with monthly or 2 week interval (doesn’t really change much)
  • preAnchorBehavior=NEXT
  • Two anchors:
    • WEEKDAY, 10, no cutoff
    • WEEKDAY, 20, no cutoff

Such selling plan creates a subscription (created on 4th of February) with the following billing cycles:

:hourglass_not_done: Cycle #1 :round_pushpin: CURRENT
Status: UNBILLED
Period: 2/4/2026, 10:21:18 AM → 2/20/2026, 10:00:00 AM
Expected Billing: 2/20/2026, 10:00:00 AM

:hourglass_not_done: Cycle #2
Status: UNBILLED
Period: 2/20/2026, 10:00:01 AM → 2/20/2026, 10:00:00 AM
Expected Billing: 2/20/2026, 10:00:00 AM

:hourglass_not_done: Cycle #3
Status: UNBILLED
Period: 2/20/2026, 10:00:01 AM → 3/10/2026, 9:00:00 AM
Expected Billing: 3/10/2026, 9:00:00 AM

:hourglass_not_done: Cycle #4
Status: UNBILLED
Period: 3/10/2026, 9:00:01 AM → 3/20/2026, 9:00:00 AM
Expected Billing: 3/20/2026, 9:00:00 AM

:hourglass_not_done: Cycle #5
Status: UNBILLED
Period: 3/20/2026, 9:00:01 AM → 4/10/2026, 10:00:00 AM
Expected Billing: 4/10/2026, 10:00:00 AM

:hourglass_not_done: Cycle #6
Status: UNBILLED
Period: 4/10/2026, 10:00:01 AM → 4/20/2026, 10:00:00 AM
Expected Billing: 4/20/2026, 10:00:00 AM

….

Questions

  1. Why is the second cycle on the 20th Feb to 20th of Feb, whereas all following cycles are correctly between the 10th and 20th, 20th to 10th of next month respectively?
  2. Why is the first cycle on the 20th of Feb and not 10th of Feb (which would be the next anchoring date from today (4th Feb)?

Thanks for the detailed writeup @mgtmn! I’ve seen a similar report about zero-length billing cycles when using multiple anchors - this unanswered thread describes the same symptom with Cycle #2 having an end date before the start :thinking:

To dig into this properly, could you share the GraphQL mutation you used to create the selling plan (with the billing/delivery policy config), plus an x-request-id from the response headers? That’ll let me check our logs and try figure out what exactly is happening here

Thank you for looking into this. Below the requested info:

x-request-id: c1a4ea8a-1cfe-44af-ba4b-6ece26a14546-1770220729

mutation against /admin/api/2026-01/graphql.json:

{
    "query": "mutation CreateSellingPlanGroup($input: SellingPlanGroupInput!, $resources: SellingPlanGroupResourceInput) { sellingPlanGroupCreate(input: $input, resources: $resources) { sellingPlanGroup { id sellingPlans(first: 31) { edges { node { id name pricingPolicies { ... on SellingPlanFixedPricingPolicy { adjustmentType adjustmentValue { ... on SellingPlanPricingPolicyPercentageValue { percentage } ... on MoneyV2 { amount currencyCode } } } } deliveryPolicy { ... on SellingPlanRecurringDeliveryPolicy { interval intervalCount anchors { type day month cutoffDay } preAnchorBehavior } } billingPolicy { ... on SellingPlanRecurringBillingPolicy { interval intervalCount anchors { type day month cutoffDay } minCycles maxCycles } } } } } } userErrors { message } } }",
    "variables": {
        "input": {
            "name": "10th and 20th VPM",
            "merchantCode": "10th-and-20th VPM",
            "options": [
                "Deliver every month"
            ],
            "sellingPlansToCreate": [
                {
                    "name": "Deliver every month",
                    "options": [
                        "Deliver every month"
                    ],
                    "category": "SUBSCRIPTION",
                    "billingPolicy": {
                        "recurring": {
                            "interval": "MONTH",
                            "intervalCount": 1,
                            "anchors": [
                                {
                                    "type": "MONTHDAY",
                                    "day": 10
                                },
                                {
                                    "type": "MONTHDAY",
                                    "day": 20
                                }
                            ]
                        }
                    },
                    "deliveryPolicy": {
                        "recurring": {
                            "interval": "MONTH",
                            "intervalCount": 1,
                            "anchors": [
                                {
                                    "type": "MONTHDAY",
                                    "day": 10
                                },
                                {
                                    "type": "MONTHDAY",
                                    "day": 20
                                }
                            ],
                            "preAnchorBehavior": "NEXT",
                            "intent": "FULFILLMENT_BEGIN"
                        }
                    },
                    "pricingPolicies": []
                }
            ]
        },
        "resources": {
            "productIds": [
                "gid://shopify/Product/7176805777541"
            ],
            "productVariantIds": [
                "gid://shopify/ProductVariant/41290861969541"
            ]
        }
    }
}

Thanks for the example @mgtmn - I can see the selling plan was created correctly with both MONTHDAY anchors (10th and 20th).

Having reviewed the logs, looks like the billing cycle weirdness you’re seeing happens downstream when a subscription contract gets created from that selling plan (i.e., during checkout). To dig into this properly, could you share:

  1. An example subscription contract ID showing the problematic billing cycles (the gid://shopify/SubscriptionContract/...)

  2. Either the order ID or the x-request-id from the checkout that created the subscription

  3. How you’re viewing the billing cycles - are you querying subscriptionBillingCycles directly, or using another method?

That’ll let me look at what’s happening during the actual cycle calculation and raise this with our Subscriptions team internally if need be - cheers!

  1. Subscription Contract: 58992984197

  2. Order ID: 5829692555397

  3. Querying subscriptionBillingCycles returns the following cycles

"subscriptionBillingCycles": {
            "nodes": [
                {
                    "cycleIndex": 1,
                    "billingAttemptExpectedDate": "2026-02-20T15:00:00Z",
                    "cycleStartAt": "2026-02-04T15:59:51Z",
                    "cycleEndAt": "2026-02-20T15:00:00Z",
                    "status": "UNBILLED",
                    "skipped": false,
                    "edited": false
                },
                {
                    "cycleIndex": 2,
                    "billingAttemptExpectedDate": "2026-02-20T15:00:00Z",
                    "cycleStartAt": "2026-02-20T15:00:01Z",
                    "cycleEndAt": "2026-02-20T15:00:00Z",
                    "status": "UNBILLED",
                    "skipped": false,
                    "edited": false
                },
                {
                    "cycleIndex": 3,
                    "billingAttemptExpectedDate": "2026-03-10T15:00:00Z",
                    "cycleStartAt": "2026-02-20T15:00:01Z",
                    "cycleEndAt": "2026-03-10T15:00:00Z",
                    "status": "UNBILLED",
                    "skipped": false,
                    "edited": false
                },
                {
                    "cycleIndex": 4,
                    "billingAttemptExpectedDate": "2026-03-20T15:00:00Z",
                    "cycleStartAt": "2026-03-10T15:00:01Z",
                    "cycleEndAt": "2026-03-20T15:00:00Z",
                    "status": "UNBILLED",
                    "skipped": false,
                    "edited": false
                },
                {
                    "cycleIndex": 5,
                    "billingAttemptExpectedDate": "2026-04-10T14:00:00Z",
                    "cycleStartAt": "2026-03-20T15:00:01Z",
                    "cycleEndAt": "2026-04-10T14:00:00Z",
                    "status": "UNBILLED",
                    "skipped": false,
                    "edited": false
                },
                {
                    "cycleIndex": 6,
                    "billingAttemptExpectedDate": "2026-04-20T14:00:00Z",
                    "cycleStartAt": "2026-04-10T14:00:01Z",
                    "cycleEndAt": "2026-04-20T14:00:00Z",
                    "status": "UNBILLED",
                    "skipped": false,
                    "edited": false
                },
                { ... }
            ]
        }

Noticeably, the checkout order (which is delayed) is scheduled to fulfill on the 10th of February. This is correct. But it’s odd that cycle #1 and cycle #2 have the 20th of February as the expected billing date. I would expect that either

  • the first cycle is scheduled to bill on the 10th of February and the second cycle is scheduled to bill on the 20th of February; OR
  • the first cycle is scheduled to bill on the 20th of February and the second cycle is scheduled to bill on the 10th of March. This is because the “checkout order” is yet to be created due to the “preAnchorBehavior”: “NEXT” logic

I also tested this with a selling plan that is exactly the same as the one above, but having “preAnchorBehavior”: “ASAP” instead of “NEXT”

  • x-request-id of selling plan creation: 1060ce8f-e199-43cb-96af-22ff168fa07d-1770236874
  • Checkout order: 5829966594181
  • Subscription contract: 59021131909
  • Now we have two cycles scheduled to bill on the 10th of February
"subscriptionBillingCycles": {
            "nodes": [
                {
                    "cycleIndex": 1,
                    "billingAttemptExpectedDate": "2026-02-10T20:00:00Z",
                    "cycleStartAt": "2026-02-04T20:28:41Z",
                    "cycleEndAt": "2026-02-10T20:00:00Z",
                    "status": "UNBILLED",
                    "skipped": false,
                    "edited": false
                },
                {
                    "cycleIndex": 2,
                    "billingAttemptExpectedDate": "2026-02-10T20:00:00Z",
                    "cycleStartAt": "2026-02-10T20:00:01Z",
                    "cycleEndAt": "2026-02-10T20:00:00Z",
                    "status": "UNBILLED",
                    "skipped": false,
                    "edited": false
                },
                {
                    "cycleIndex": 3,
                    "billingAttemptExpectedDate": "2026-02-20T20:00:00Z",
                    "cycleStartAt": "2026-02-10T20:00:01Z",
                    "cycleEndAt": "2026-02-20T20:00:00Z",
                    "status": "UNBILLED",
                    "skipped": false,
                    "edited": false
                },
                {
                    "cycleIndex": 4,
                    "billingAttemptExpectedDate": "2026-03-10T20:00:00Z",
                    "cycleStartAt": "2026-02-20T20:00:01Z",
                    "cycleEndAt": "2026-03-10T20:00:00Z",
                    "status": "UNBILLED",
                    "skipped": false,
                    "edited": false
                },
                {
                    "cycleIndex": 5,
                    "billingAttemptExpectedDate": "2026-03-20T20:00:00Z",
                    "cycleStartAt": "2026-03-10T20:00:01Z",
                    "cycleEndAt": "2026-03-20T20:00:00Z",
                    "status": "UNBILLED",
                    "skipped": false,
                    "edited": false
                },
                {
                    "cycleIndex": 6,
                    "billingAttemptExpectedDate": "2026-04-10T19:00:00Z",
                    "cycleStartAt": "2026-03-20T20:00:01Z",
                    "cycleEndAt": "2026-04-10T19:00:00Z",
                    "status": "UNBILLED",
                    "skipped": false,
                    "edited": false
                }
            ]
        }

Thanks for the detailed writeup and billing cycle data, really helpful.

I had a good look into this internally and found the behavior you’re seeing with the zero-duration Cycle #2 (Feb 20 to Feb 20) and the first billing landing on the 20th instead of the 10th is consistent with how the billing cycle calculation currently handles multiple anchors on a single selling plan. When there are two anchors, the system creates billing cycle “boundaries” at each anchor point within the billing period, and this can produce unexpected cycle lengths, including that zero-duration cycle you’re seeing. The first anchor selection with preAnchorBehavior: NEXT can also resolve to a different anchor than you’d expect when multiple are present.

I’d like to get you a more definitive answer on the specifics here though. If you reach out via https://help.shopify.com/en and click “Chat with a human” in the bottom right, our support team can look at the actual contracts and orders with proper authentication and give you a concrete answer on whether this is something that can be addressed or if a workaround is needed. You can reference this thread so they have the context.

In the meantime, one approach that other developers have used to get reliable billing on two specific days per month is to create two separate selling plans (one anchored on the 10th, one on the 20th) rather than a single plan with two anchors. That avoids the multi-anchor boundary calculation entirely and gives you predictable cycles from the start albeit with added complexity.