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 for straightforward migrations to public plans.
Anchor to When to use the subscription migration commandsWhen 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.
Anchor to RequirementsRequirements
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.
- The Partner Dashboard account that you use with Shopify CLI belongs to the app's Partner organization and has the Manage app listings permission. The View financials permission alone doesn't grant access to the subscription migration commands.
Anchor to Step 1: Set up Shopify CLIStep 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.
Anchor to 1. Install the nightly build of Shopify CLI1. 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
Anchor to 2. Select the app2. 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.
Anchor to Step 2: List migratable subscriptionsStep 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
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
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.
Anchor to Step 3: Prepare the schedule CSVStep 3: Prepare the schedule CSV
Complete the following steps for every shop that you want to migrate.
Anchor to 1. Create the CSV1. Create the CSV
Create a CSV with one row for each shop:
migrations.csv
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. |
Anchor to 2. Set the price behavior2. 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. |
Anchor to 3. Set the notification behavior3. 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. |
Anchor to Opt-out notificationsOpt-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.
Anchor to Step 4: Schedule the migrationsStep 4: Schedule the migrations
Submit the schedule CSV, wait for the operations to finish, and then confirm the result for every shop.
Anchor to 1. Submit the schedule1. Submit the schedule
Run schedule with the CSV path:
Terminal
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
Anchor to 2. Check operation status2. 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
For an input larger than 250 shops, repeat --id for every operation that schedule returned:
Terminal
For the meaning of each status, refer to Operation statuses.
Anchor to 3. Review per-shop results3. 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
The JSON output includes operation metadata and each shop's result:
Output
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.
Anchor to Manage submitted migrationsManage submitted migrations
After you submit a schedule, use the following commands as needed to stop or reverse migrations that haven't completed.
Anchor to Cancel unprocessed workCancel unprocessed work
Use cancel to stop an operation from processing additional shops:
Terminal
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.
Anchor to Unschedule subscriptionsUnschedule subscriptions
The unschedule CSV requires only a shop_id column:
migrations-to-unschedule.csv
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
Unscheduling reverses subscriptions that are still scheduled. It isn't a rollback after a subscription has migrated.
Anchor to Run non-interactivelyRun 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.
--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.
--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
Anchor to Operation statusesOperation 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. |
Anchor to Per-shop result codesPer-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. |
Anchor to Troubleshoot accessTroubleshoot access
If subscription migrations are temporarily paused, you might receive the following message:
Output
No migration operation is created when you receive this message. Existing subscriptions continue billing through the Billing API. Retry the command later.
Anchor to App isn't available to your accountApp 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
Output
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.
- Log in to the Partner Dashboard with the account that you use for Shopify CLI.
- Confirm that the app appears in your organization.
- Confirm that your account has the Manage app listings permission.
- Confirm that
--path,--config, or--client-idselects the intended app.