Skip to main content

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.

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

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

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

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

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.


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.

Create a CSV with one row for each shop:

migrations.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:

FieldRequiredDescription
shop_idYesThe numeric shop ID or a gid://shopify/Shop/<id> GID.
target_plan_handleYesThe handle of the public or private Shopify App Pricing plan to migrate the subscription onto.
price_behaviorYesThe pricing behavior to apply. Set this column to HONOR_BILLING_PRICE or PLAN_PRICE.
notificationNoThe 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:

ValueBehavior
HONOR_BILLING_PRICEKeep the existing subscription billing price after migration.
PLAN_PRICEApply 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:

ValueBehavior
WHEN_REQUIREDNotify the merchant only when a notice is required. This is the default.
OPT_OUTNotify 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

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

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

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

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

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.

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

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

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

Output

{
"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.


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

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.

Anchor to Unschedule subscriptionsUnschedule subscriptions

The unschedule CSV requires only a shop_id column:

migrations-to-unschedule.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

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.


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.

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

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

StatusMeaning
RUNNINGThe operation is still processing shops.
COMPLETEDProcessing finished. Inspect every per-shop result to determine the outcome.
FAILEDThe operation failed. Inspect its per-shop results before retrying.
CANCELEDCancellation stopped the operation from processing additional shops.

Anchor to Per-shop result codesPer-shop result codes

CodeMeaning
SCHEDULEDThe migration was scheduled for the shop.
CANCELEDThe scheduled migration was canceled for the shop.
INVALID_PLANThe target plan handle isn't valid.
INELIGIBLEThe shop's subscription isn't eligible for migration.
BLOCKEDThe migration is blocked for the shop.
ALREADY_SCHEDULEDA migration is already scheduled for the shop.
ALREADY_MIGRATEDThe shop's subscription has already migrated.
NOT_FOUNDNo matching subscription was found for the shop.
INTERNAL_ERRORAn internal error prevented processing for the shop. Check the operation again before retrying.

Anchor to Migrations are temporarily unavailableMigrations are temporarily unavailable

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

Output

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.

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

App not found

Output

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 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.
  4. Confirm that --path, --config, or --client-id selects the intended app.


Was this page helpful?