---
title: Migrating from Shopify
sort: 2
tags: [migration]
---

# Migrating from Shopify

A migration takes between an afternoon (10 SKUs, no historical orders) and several days (100k SKUs,
two years of order history). Three phases.

## Phase 1 — Export from Shopify

Use Shopify's GraphQL Admin API. The CLI has a helper that runs the exact queries we need:

```bash
zephyr migrate shopify pull \
  --shop my-shop.myshopify.com \
  --token shpat_… \
  --out ./shopify-export/
```

This writes one JSON file per resource type: `products.jsonl`, `customers.jsonl`,
`orders.jsonl`, `collections.jsonl`. Each line is one Shopify record, untouched.

## Phase 2 — Map and validate

Mapping is where most of the work lives. The CLI's default rules cover the 80 % case:

| Shopify field               | ZephyrCart field                                  |
|-----------------------------|---------------------------------------------------|
| `product.handle`            | `product.sku` (if no other SKU set)               |
| `product.product_type`      | First collection assignment                        |
| `variant.sku`               | `variant.sku`                                     |
| `variant.price` (string)    | `variant.default_price_cents` (× 100)             |
| `customer.email`            | `customer.email`                                  |
| `customer.default_address`  | `customer.addresses[0]`, `is_default: true`       |
| `order.line_items[]`        | `order.line_items[]`                              |

Override any rule with a `mapping.toml` next to the export:

```toml
[product]
sku_from = "variants[0].sku"

[customer]
exclude_if_marketing_opt_out = false
```

Validate before importing:

```bash
zephyr migrate shopify validate --in ./shopify-export/ --mapping ./mapping.toml
```

The validator reports field-by-field anything ZephyrCart can't accept (currencies it doesn't
recognise, SKUs longer than its limit, etc.). Fix at the source, re-export, re-validate.

## Phase 3 — Import

```bash
zephyr migrate shopify push --in ./shopify-export/ --mapping ./mapping.toml --concurrency 8
```

The CLI streams resources to the API with idempotency keys derived from the Shopify ID, so a
re-run of an interrupted import is safe. Progress is checkpointed to `./shopify-export/.zephyr-state`.

## What we do not migrate

- **Shopify scripts** — no equivalent in ZephyrCart; checkout rules express most of what scripts do.
- **Shopify Markets** — model the same shape with our [tax zones](../api-reference/products.md).
- **Custom apps' metafields** — these come through as `product.metadata` keys prefixed with the
  app handle; review and rename before going live.
