$npx -y skills add 40RTY-ai/shopify-admin-skills --skill shopify-admin-subscription-mrr-trackerRead-only: for stores with subscription products, calculates MRR, ARR, active subscriber count, and rolling churn rate from subscription contracts.
| 1 | ## Purpose |
| 2 | For stores selling subscription products via Shopify's native subscriptions, this skill aggregates active subscription contracts into the standard SaaS-style metrics finance teams care about: monthly recurring revenue (MRR), annualized recurring revenue (ARR), active subscriber count, average revenue per subscriber (ARPU), and a rolling churn rate. Read-only — no mutations. |
| 3 | |
| 4 | ## Prerequisites |
| 5 | - Authenticated Shopify CLI session: `shopify store auth --store <domain> --scopes read_own_subscription_contracts` |
| 6 | - API scopes: `read_own_subscription_contracts` |
| 7 | |
| 8 | ## Parameters |
| 9 | |
| 10 | | Parameter | Type | Required | Default | Description | |
| 11 | |-----------|------|----------|---------|-------------| |
| 12 | | store | string | yes | — | Store domain (e.g., mystore.myshopify.com) | |
| 13 | | churn_window_days | integer | no | 30 | Rolling window for churn calculation (cancellations / starting subscribers) | |
| 14 | | as_of | string | no | today (UTC) | ISO date for "as of" snapshot label | |
| 15 | | include_paused | bool | no | false | Treat `PAUSED` contracts as active recurring revenue | |
| 16 | | format | string | no | human | Output format: `human` or `json` | |
| 17 | |
| 18 | ## Safety |
| 19 | |
| 20 | > ℹ️ Read-only skill — no mutations are executed. **This skill is only meaningful if the store sells subscription products.** If `subscriptionContracts` returns 0 contracts, the report exits with $0 MRR — that is the correct answer, not an error. |
| 21 | |
| 22 | ## Recurring Revenue Model |
| 23 | |
| 24 | For each subscription contract, the contribution to MRR depends on its billing interval: |
| 25 | |
| 26 | ``` |
| 27 | billing_amount = sum(line.currentPrice × line.quantity) for each line in contract |
| 28 | mrr_contribution = |
| 29 | billing_amount if interval = MONTH and intervalCount = 1 |
| 30 | billing_amount / intervalCount if interval = MONTH (e.g., every 3 months → /3) |
| 31 | billing_amount × 4.345 if interval = WEEK (avg weeks per month) |
| 32 | billing_amount × 4.345 / intervalCount if interval = WEEK with intervalCount > 1 |
| 33 | billing_amount / 12 if interval = YEAR |
| 34 | billing_amount × 30 / intervalCount if interval = DAY |
| 35 | ``` |
| 36 | |
| 37 | Aggregations: |
| 38 | - **MRR** = Σ mrr_contribution for all `ACTIVE` (and `PAUSED` if `include_paused`) contracts |
| 39 | - **ARR** = MRR × 12 |
| 40 | - **Active subscribers** = unique `customer.id` count |
| 41 | - **ARPU** = MRR / active subscribers |
| 42 | - **Churn rate (period)** = contracts cancelled inside `churn_window_days` ÷ contracts that were active at the start of the window |
| 43 | - **Net new subscribers** = new contracts inside the window − cancelled contracts inside the window |
| 44 | |
| 45 | ## Workflow Steps |
| 46 | |
| 47 | 1. **OPERATION:** `subscriptionContracts` — query |
| 48 | **Inputs:** `first: 250`, select `id`, `status`, `createdAt`, `updatedAt`, `nextBillingDate`, `customer { id, displayName }`, `currencyCode`, `billingPolicy { interval, intervalCount }`, `lines { edges { node { currentPrice { amount, currencyCode }, quantity, productId, variantId, title } } }`, pagination cursor |
| 49 | **Expected output:** All subscription contracts; paginate until `hasNextPage: false` |
| 50 | |
| 51 | 2. Classify each contract by status: `ACTIVE`, `PAUSED`, `CANCELLED`, `EXPIRED`, `FAILED` |
| 52 | |
| 53 | 3. Compute `mrr_contribution` for each active (and paused if requested) contract using the model above |
| 54 | |
| 55 | 4. Aggregate MRR / ARR / subscribers / ARPU |
| 56 | |
| 57 | 5. For churn: count cancellations where `updatedAt` is within `churn_window_days` and status moved to `CANCELLED`; count contracts that existed and were `ACTIVE` at `as_of - churn_window_days` |
| 58 | |
| 59 | 6. Build a per-product breakdown so finance can see which subscription SKUs drive MRR |
| 60 | |
| 61 | ## GraphQL Operations |
| 62 | |
| 63 | ```graphql |
| 64 | # subscriptionContracts:query — validated against api_version 2025-01 |
| 65 | query SubscriptionMRR($after: String) { |
| 66 | subscriptionContracts(first: 250, after: $after) { |
| 67 | edges { |
| 68 | node { |
| 69 | id |
| 70 | status |
| 71 | createdAt |
| 72 | updatedAt |
| 73 | nextBillingDate |
| 74 | currencyCode |
| 75 | customer { |
| 76 | id |
| 77 | displayName |
| 78 | defaultEmailAddress { emailAddress } |
| 79 | } |
| 80 | billingPolicy { interval intervalCount minCycles maxCycles } |
| 81 | deliveryPolicy { interval intervalCount } |
| 82 | lines(first: 50) { |
| 83 | edges { |
| 84 | node { |
| 85 | id |
| 86 | productId |
| 87 | variantId |
| 88 | title |
| 89 | quantity |
| 90 | currentPrice { amount currencyCode } |
| 91 | } |
| 92 | } |
| 93 | } |
| 94 | } |
| 95 | } |
| 96 | pageInfo { hasNextPage endCursor } |
| 97 | } |
| 98 | } |
| 99 | ``` |
| 100 | |
| 101 | ## Session Tracking |
| 102 | |
| 103 | **Claude MUST emit the following output at each stage. This is mandatory.** |
| 104 | |
| 105 | **On start**, emit: |
| 106 | ``` |
| 107 | ╔══════════════════════════════════════════════╗ |
| 108 | ║ SKILL: Su |