---
title: Migrate subscriptions with Shopify CLI
description: >-
  Use Shopify CLI to migrate existing manual-billing subscriptions to Shopify
  App Pricing plans.
source_url:
  html: >-
    https://shopify.dev/docs/apps/launch/billing/shopify-app-pricing/migration-api
  md: >-
    https://shopify.dev/docs/apps/launch/billing/shopify-app-pricing/migration-api.md
---

# Migrate subscriptions with Shopify CLI

Use Shopify CLI to migrate existing manual-billing subscriptions to Shopify App Pricing when they require a complex migration, such as usage-based subscriptions or price mismatches. Use the subscription migration UI in the [Partner Dashboard](https://www.shopify.com/partners) for straightforward migrations to public plans.

***

## When to use the subscription migration commands

Creating Shopify App Pricing plans and enabling App Pricing affects new subscriptions only. Existing Billing API subscriptions continue billing through the Billing API until migration tooling moves them.

Use the subscription migration UI in the Partner Dashboard for existing subscriptions that map directly to one of your public Shopify App Pricing plans.

Use the subscription migration commands in Shopify CLI for complex migrations, including:

* **Usage-based subscriptions**: Subscriptions that bill on metered usage rather than only a fixed recurring price.
* **Price mismatches**: Subscriptions whose current price doesn't match the target Shopify App Pricing plan.
* **Private plan migrations**: Migrations that move a subscription to a private Shopify App Pricing plan.

***

## Requirements

Before you migrate subscriptions, you must meet the following requirements:

* Your app is enabled for Shopify App Pricing, and its manual-billing subscriptions are eligible for migration. A subscription with an active discount can't be migrated until the discount ends.
* You've created target Shopify App Pricing plans and recorded their plan handles. Targets can be public or private plans. Private plans are available as migration targets to all Shopify CLI users.
* You've met the [Shopify CLI requirements](https://shopify.dev/docs/api/shopify-cli#requirements).
* The Partner Dashboard account that you use with Shopify CLI belongs to the app's Partner organization and has the [**Manage app listings**](https://help.shopify.com/partners/dashboard/account-access) permission. The **View financials** permission alone doesn't grant access to the subscription migration commands.

***

## Step 1: Set up Shopify CLI

Install the Shopify CLI build that includes the subscription migration commands, and then select the app whose subscriptions you want to migrate.

### 1.​Install the nightly build of Shopify CLI

The subscription migration commands are available in the nightly build of Shopify CLI. Install the nightly build, and then verify the installed version:

## Terminal

```bash
npm install -g @shopify/cli@nightly
shopify version
```

### 2.​Select the app

Run the commands from a linked app project. By default, Shopify CLI uses the Client ID from the active app configuration. Use `--path` to select a different app project. Within that project, use either `--config` to select an app configuration or `--client-id` to select another accessible app. You can't use `--config` and `--client-id` together.

***

## Step 2: List migratable subscriptions

Start by listing the subscriptions that are eligible for migration. Use the `UNSCHEDULED` filter to find subscriptions that haven't been scheduled or migrated:

## Terminal

```bash
shopify app subscription-migrations list \
  --status UNSCHEDULED \
  > migratable-subscriptions.csv
```

The command fetches every page of subscriptions before completing. By default, it streams a comprehensive CSV to standard output. Shopify CLI writes the header immediately and then writes each page as it arrives, so a large result doesn't need to be held in memory. If a later page request fails, the command exits with an error and a redirected CSV can contain a valid partial inventory. Check the command's exit status before using the file to prepare a migration.

Use `--json` when you need structured output. JSON output is buffered until every page has been fetched successfully, so Shopify CLI doesn't write a partial JSON document:

## Terminal

```bash
shopify app subscription-migrations list \
  --json \
  > subscriptions.json
```

You can filter by `UNSCHEDULED`, `SCHEDULED`, or `MIGRATED`. If you omit `--status`, the command lists all three statuses.

The list CSV includes the shop ID, migration status, current manual subscription details, target plan details, notification details, price behavior, effective date, and last failure reason. Historical list output can include `notification_kind=NONE`; `NONE` isn't valid when scheduling a migration. When you create the schedule CSV, use only `WHEN_REQUIRED` or `OPT_OUT` for the `notification` column.

The list output is an inventory, not schedule input. Review the inventory, select the shops you want to migrate, and create a schedule CSV with `shop_id`, `target_plan_handle`, `price_behavior`, and `notification` columns before running `schedule`.

***

## Step 3: Prepare the schedule CSV

Complete the following steps for every shop that you want to migrate.

### 1.​Create the CSV

Create a CSV with one row for each shop:

## migrations.csv

```csv
shop_id,target_plan_handle,price_behavior,notification
gid://shopify/Shop/123456789,pro,HONOR_BILLING_PRICE,WHEN_REQUIRED
```

The schedule CSV supports these fields:

| Field | Required | Description |
| - | - | - |
| `shop_id` | Yes | The numeric shop ID or a `gid://shopify/Shop/<id>` GID. |
| `target_plan_handle` | Yes | The handle of the public or private Shopify App Pricing plan to migrate the subscription onto. |
| `price_behavior` | Yes | The pricing behavior to apply. Set this column to `HONOR_BILLING_PRICE` or `PLAN_PRICE`. |
| `notification` | No | The notification behavior to apply. Set this column to `WHEN_REQUIRED` or `OPT_OUT`. Defaults to `WHEN_REQUIRED` when omitted or blank. |

### 2.​Set the price behavior

For each row, set the `price_behavior` column to one of the following values:

| Value | Behavior |
| - | - |
| `HONOR_BILLING_PRICE` | Keep the existing subscription billing price after migration. |
| `PLAN_PRICE` | Apply the target plan's price after migration. |

### 3.​Set the notification behavior

For each row, set the optional `notification` column to one of the following values:

| Value | Behavior |
| - | - |
| `WHEN_REQUIRED` | Notify the merchant only when a notice is required. This is the default. |
| `OPT_OUT` | Notify the merchant and let them opt out of the migration. |

#### Opt-out notifications

An opt-out notification might be required whenever a migration could change the amount that a merchant pays. This includes moving to a plan with a higher or lower recurring price. A target plan with usage pricing is also considered a potential billing change.

***

## Step 4: Schedule the migrations

Submit the schedule CSV, wait for the operations to finish, and then confirm the result for every shop.

### 1.​Submit the schedule

Run `schedule` with the CSV path:

## Terminal

```bash
shopify app subscription-migrations schedule \
  --input migrations.csv \
  --watch
```

Before asking for confirmation, Shopify CLI normalizes and sorts the shop IDs and validates the complete CSV. If any row is invalid, Shopify CLI doesn't submit any migration operation. After confirmation, Shopify CLI splits the input into batches of at most 250 shops and submits one asynchronous operation per batch.

Multi-batch submission isn't atomic. A later batch can fail after Shopify CLI has accepted earlier batches. Save every accepted operation ID as it's returned. You need every ID to check status or cancel unprocessed work. With `--watch`, Shopify CLI displays progress until every submitted operation reaches a terminal state.

You can also pipe CSV data through standard input:

## Terminal

```bash
cat migrations.csv | shopify app subscription-migrations schedule --watch
```

### 2.​Check operation status

If you submitted the schedule with `--watch`, then Shopify CLI already displayed progress until every operation reached a terminal state, and you can skip to reviewing per-shop results. Otherwise, pass an operation ID to `status`:

## Terminal

```bash
shopify app subscription-migrations status \
  --id 'gid://shopify/AppSubscriptionMigrationOperation/123' \
  --watch
```

For an input larger than 250 shops, repeat `--id` for every operation that `schedule` returned:

## Terminal

```bash
shopify app subscription-migrations status \
  --id 'gid://shopify/AppSubscriptionMigrationOperation/123' \
  --id 'gid://shopify/AppSubscriptionMigrationOperation/124' \
  --watch
```

For the meaning of each status, refer to [Operation statuses](#operation-statuses).

### 3.​Review per-shop results

An operation with a `COMPLETED` status has finished processing, but individual shop actions might not have succeeded. Human-readable output shows the operation status and settled shop count. Use `--json` to inspect every per-shop result before treating the operation as successful:

## Terminal

```bash
shopify app subscription-migrations status \
  --id 'gid://shopify/AppSubscriptionMigrationOperation/123' \
  --json
```

The JSON output includes operation metadata and each shop's result:

## Output

```json
{
  "schemaVersion": 1,
  "operations": [
    {
      "id": "gid://shopify/AppSubscriptionMigrationOperation/123",
      "status": "COMPLETED",
      "total": 1,
      "results": {
        "edges": [
          {
            "node": {
              "shopId": "gid://shopify/Shop/123456789",
              "code": "SCHEDULED"
            }
          }
        ]
      }
    }
  ]
}
```

To poll until the operation is terminal and then receive one structured document, combine `--json` and `--watch`. For the meaning of each `code`, refer to [Per-shop result codes](#per-shop-result-codes).

***

## Manage submitted migrations

After you submit a schedule, use the following commands as needed to stop or reverse migrations that haven't completed.

### Cancel unprocessed work

Use `cancel` to stop an operation from processing additional shops:

## Terminal

```bash
shopify app subscription-migrations cancel \
  --id 'gid://shopify/AppSubscriptionMigrationOperation/123'
```

Repeat `--id` to cancel multiple operations. Canceling doesn't undo shops that have already been scheduled or migrated. To reverse subscriptions that are still scheduled, use `unschedule`.

### Unschedule subscriptions

The `unschedule` CSV requires only a `shop_id` column:

## migrations-to-unschedule.csv

```csv
shop_id
gid://shopify/Shop/123456789
```

You can also reuse the CSV that you submitted to `schedule`. The `unschedule` command ignores `target_plan_handle`, `price_behavior`, and `notification` columns. You can also use `unschedule` to cancel migrations scheduled in the Partner Dashboard. As with `schedule`, Shopify CLI validates the complete input before asking for confirmation, submits batches of at most 250 shops sequentially, and returns an operation ID for each accepted batch.

Multi-batch `unschedule` submission isn't atomic. A later batch can fail after earlier batches were accepted. Save every accepted operation ID so that you can inspect or cancel each operation.

## Terminal

```bash
shopify app subscription-migrations unschedule \
  --input migrations-to-unschedule.csv \
  --watch
```

Unscheduling reverses subscriptions that are still scheduled. It isn't a rollback after a subscription has migrated.

***

## Run non-interactively

By default, `schedule` and `unschedule` ask you to confirm the action and subscription count before submission. The `--force` flag skips this confirmation and is required in non-interactive environments.

**Skipping confirmation:**

`--force` skips the prompt that confirms the action and subscription count. It doesn't make multi-batch submission atomic. Validate the input and intended app before using it.

Use `--json` for machine-readable output. With `--json --watch`, Shopify CLI writes one structured JSON document after all operations reach a terminal state:

## Terminal

```bash
shopify app subscription-migrations schedule \
  --input migrations.csv \
  --force \
  --json \
  --watch
```

***

## Operation statuses

| Status | Meaning |
| - | - |
| `RUNNING` | The operation is still processing shops. |
| `COMPLETED` | Processing finished. Inspect every per-shop result to determine the outcome. |
| `FAILED` | The operation failed. Inspect its per-shop results before retrying. |
| `CANCELED` | Cancellation stopped the operation from processing additional shops. |

***

## Per-shop result codes

| Code | Meaning |
| - | - |
| `SCHEDULED` | The migration was scheduled for the shop. |
| `CANCELED` | The scheduled migration was canceled for the shop. |
| `INVALID_PLAN` | The target plan handle isn't valid. |
| `INELIGIBLE` | The shop's subscription isn't eligible for migration. |
| `BLOCKED` | The migration is blocked for the shop. |
| `ALREADY_SCHEDULED` | A migration is already scheduled for the shop. |
| `ALREADY_MIGRATED` | The shop's subscription has already migrated. |
| `NOT_FOUND` | No matching subscription was found for the shop. |
| `INTERNAL_ERROR` | An internal error prevented processing for the shop. Check the operation again before retrying. |

***

## Troubleshoot access

### Migrations are temporarily unavailable

If subscription migrations are temporarily paused, you might receive the following message:

## Output

```text
The App Subscription Migration API is temporarily unavailable. Please try again later.
```

No migration operation is created when you receive this message. Existing subscriptions continue billing through the Billing API. Retry the command later.

### App isn't available to your account

If your Partner Dashboard account doesn't have the **Manage app listings** permission, you might receive one of the following messages:

## Output

```text
App not found
```

## Output

```text
The caller is missing the required Manage app listings permission
```

The `App not found` message is intentionally non-disclosing. It can also mean that the selected app doesn't belong to your organization or that the app context is incorrect.

1. Log in to the [Partner Dashboard](https://www.shopify.com/partners) with the account that you use for Shopify CLI.
2. Confirm that the app appears in your organization.
3. Confirm that your account has the [**Manage app listings** permission](https://help.shopify.com/partners/dashboard/account-access).
4. Confirm that `--path`, `--config`, or `--client-id` selects the intended app.

***

## Related

* [Migrate to Shopify App Pricing](https://shopify.dev/docs/apps/launch/billing/shopify-app-pricing/migrating-to-shopify-app-pricing)
* [Shopify App Pricing](https://shopify.dev/docs/apps/launch/billing/shopify-app-pricing)
* [Shopify CLI](https://shopify.dev/docs/api/shopify-cli)

***
