Documentation

Everything Shop Shift does, how it does it, and what to do when it doesn't.

Contents

  1. Overview
  2. Requirements
  3. Getting started
  4. What gets migrated
  5. Field mapping reference
  6. What is not migrated
  7. How the migration engine works
  8. Re-running and idempotency
  9. Error handling and field shedding
  10. Plans and limits
  11. Progress, logs and notifications
  12. Retrying failed items
  13. API reference
  14. Security model
  15. Troubleshooting
  16. Glossary

1. Overview

Shop Shift is an embedded Shopify app that copies data from a WooCommerce store into a Shopify store. It reads WooCommerce through its REST API v3 and writes to Shopify through the Admin GraphQL API.

Three design rules shape everything else:

2. Requirements

RequirementDetail
WordPress5.6 or later
WooCommerce5.0 or later
REST APIv3 enabled, reachable from the internet over HTTPS
CredentialsConsumer key + secret with at least Read access
ShopifyAny plan that allows app installation
The most common blocker. Your WooCommerce REST API must be publicly reachable. Stores behind a firewall, an IP allowlist, HTTP basic auth, "coming soon"/maintenance mode, or an aggressive security plugin will fail to connect — nothing can be read, so nothing can be migrated.

3. Getting started

Step 1 — Create WooCommerce API keys

In WordPress admin go to WooCommerce → Settings → Advanced → REST API → Add key:

Copy the consumer key (ck_…) and secret (cs_…) — WooCommerce shows the secret only once.

Step 2 — Connect the store

In Shop Shift open WooCommerce, paste the store URL, key and secret, then press Test connection. On success you will see your WooCommerce and WordPress versions. Your credentials are encrypted before they are stored and are only ever shown back masked.

Step 3 — Start a migration

Migrations → New migration. Pick what to migrate; the wizard shows how many records exist in WooCommerce and how many your plan covers. Anything your plan does not include is disabled with the reason shown.

Optional settings on this screen:

Step 4 — Let it run

Press start and close the tab if you like. Progress updates every few seconds while you watch, and the Activity log shows what is happening in real time.

4. What gets migrated

EntityBecomesIncludes
ProductsShopify products Title, description, vendor, product type, status, tags, prices, compare-at prices, SKUs, inventory and tracking, weight and unit, requires-shipping, options, variants, product and variant images, metafields, brands, collection membership
CategoriesCustom collections Title, description, category image. Products are linked to their collections automatically
CustomersShopify customers First and last name, email, phone (reformatted to E.164), billing address, custom tags
OrdersShopify orders Line items, shipping lines, billing and shipping addresses, currency, financial status, original order date, a matching sale transaction for paid orders, tax lines, discount codes, tags
CouponsDiscount codes Percentage, fixed-cart and fixed-product coupons with their amount, usage limits and expiry. Restrictions Shopify cannot express are named in a warning on the record rather than dropped silently
BlogsShopify blogs Your WordPress post categories become blogs. Selecting blogs also selects blog posts, and vice versa — a blog with no posts in it is an empty container
Blog postsShopify articles Title, body, excerpt, original publish date, URL handle, featured image, SEO title and description. Comments are stored as data on the article
Product reviewsA product metafield Rating, author, date, verified flag and content for every review, saved onto the product. Requires Products to be migrated as well
Variants, options and images are not separate choices. They are migrated inline with each product in a single operation, which is faster and avoids half-built products.
Shopify has no built-in product reviews. The Admin API cannot create them, and Shopify's own reviews app was retired — so reviews are preserved as structured data on each product instead of being thrown away. Displaying them on your storefront needs a review app or a theme block that reads the woocommerce.reviews metafield. Reviews attach to products, so Products must be part of the same migration.
Images inside descriptions are optional. By default a product or category description keeps pointing at the images on your WooCommerce store, which breaks if you switch that store off. Tick Copy images embedded in descriptions in the wizard to upload them to Shopify and rewrite the links. It costs one upload per new image, so it is off by default; the same image reused across the catalogue is only ever uploaded once.
Image size is the biggest lever you have over speed. Shopify downloads every image itself before a product is saved, so the wizard lets you choose the resized copies WordPress already generated (Standard, up to 1024px), Large, or the original files. Original is the slowest option by a wide margin.

5. Field mapping reference

Products

WooCommerceShopifyNotes
nametitle
descriptiondescriptionHtmlHTML preserved
statusstatuspublishACTIVE, everything else → DRAFT
regular_price / sale_priceprice / compareAtPriceWhen on sale, sale becomes the price and regular becomes compare-at
skuinventoryItem.sku
manage_stock, stock_quantitytracked, inventoryQuantitiesSet at your primary location
backordersinventoryPolicyAnything other than "no" becomes CONTINUE
weightmeasurement.weightUnit is the one you pick in the wizard
virtualrequiresShippingInverted
attributes (variation-enabled)productOptionsColour, size, etc.
variationsvariantsMatched by option combination
images + variation imagesfiles + variant mediaDe-duplicated by source URL
categoriescollectionsResolved through the mapping table
brandsvendor, a metafield, and optionally tagsFirst brand becomes the vendor
meta_datametafieldsNamespaced woocommerce; a definition is created per key so they appear in your admin

Customers

WooCommerceShopifyNotes
first_name, last_namefirstName, lastNameFalls back to the billing name
emailemailRequired — customers without one are skipped
billing.phonephoneConverted to E.164 using the billing country
billing.*addresses[]Address 1/2, city, province, country, ZIP

Phone numbers are the most common source of customer rejections. WooCommerce stores whatever the shopper typed ("0300 1234567"); Shopify requires international format. Shop Shift uses the billing country to add the correct calling code and strip the national trunk prefix. If a number still cannot be made valid it is left out and the customer is migrated without it.

Orders

WooCommerce statusShopify financial status
completed, processingPAID
refundedREFUNDED
cancelled, failedVOIDED
pending, on-holdPENDING

Orders are created with their original date, inventory is bypassed so historical orders do not move your stock, and no receipts are sent to customers.

Known limitation. Order line items are created as custom line items rather than being linked to the migrated product variants. Titles, quantities and totals are correct, and the SKU is kept in the line item title — but the orders are not connected to your product records.

This is deliberate. Shopify treats a line item's SKU as a product reference, and when it matches no existing variant it silently creates a draft product called Import placeholder for {sku}. Importing historical orders that way would bury your real catalogue under hundreds of empty drafts.

6. What is not migrated

7. How the migration engine works

Starting a migration creates a plan and then walks it:

Start
  ├─ count each selected entity in WooCommerce
  ├─ cap each total to your plan limit
  ├─ create batch rows (one per page of source data)
  └─ dispatch the first batch

Batch  (repeats until none are left)
  ├─ fetch one page from WooCommerce
  ├─ migrate each item into Shopify
  ├─ checkpoint after every item
  ├─ stop after ~35 seconds and continue in a fresh job
  └─ hand over to the next batch

Finalize
  ├─ recompute the final counters
  ├─ set the overall status
  └─ send the completion email if requested

Three properties make a run hard to break:

A watchdog sweeps for runs that have stopped making progress for any reason outside the app and resumes them automatically.

Dependency order

Entities always run in an order that satisfies their dependencies: categories → products (so products can join their collections) and customers → orders (so orders can be associated by email).

8. Re-running and idempotency

Every migrated record is written to a mapping table keyed by (shop, entity type, WooCommerce ID). Before creating anything, Shop Shift looks the record up.

SituationWhat happens on a re-run
Product or category already migratedUpdated in place with the current WooCommerce data
Product or category deleted in ShopifyRecreated and the mapping repaired
Customer or order already migratedSkipped — they are not overwritten
Record failed last timeRetried

The practical result: running the same migration twice never gives you two of anything.

9. Error handling and field shedding

Real WooCommerce stores contain data Shopify refuses. Rather than failing the whole record, Shop Shift removes the offending optional field and tries again, up to three rounds — so several bad fields are shed one after another. The record lands, and a warning tells you exactly what was left out.

RecordFields that can be droppedFields that never are
ProductImages, metafields, collection links, tags, vendor, product type, descriptionTitle, options, variants
CategoryImage, descriptionTitle
CustomerPhone, addresses, tagsEmail
OrderPayment record, addresses, shipping line, tags, note, original dateLine items, currency, financial status

If a rejection touches something required, or an error set mixes an optional problem with a real one, the record still fails — loudly and with the reason — so genuine problems never hide behind a partial success.

There is one shortcut worth knowing: if several products in a row have their images rejected, Shop Shift concludes the store's media is unreachable, logs a warning, and stops sending images for the rest of that run. This roughly halves the work on a store whose uploads folder is gone.

Rate limits

Shopify throttling is expected, not exceptional. When it happens the batch is re-queued for after the throttle window and resumes exactly where it stopped. Nothing is lost and nothing is duplicated.

How fast a migration is allowed to run is derived from your store’s real Shopify API budget, which the app reads from Shopify’s own responses rather than assuming. A store on a larger Shopify plan is given a larger budget, so it is migrated faster.

Why orders are slower than products

Shopify limits order creation separately from the ordinary API budget, and that limit is small. It is also the only limit Shopify does not publish a number for — it is reported as an ordinary error with no timing attached. So orders carry their own ceiling on how many can be created at once, on top of everything else:

Your Shopify planOrders created in parallel
Basic2
Grow2
Advanced3
Plus6
Enterprise / Commerce Components8
This is your Shopify plan, not your Shop Shift plan. The table in section 10 is about how many records Shop Shift will migrate for you. This one is about how quickly Shopify lets us create them, and it is set by whichever Shopify plan your store is on. Basic and Grow appear together because Shopify gives them the same API budget.

Because Shopify does not publish the order limit, these figures are a starting point rather than a promise. If Shopify refuses at that rate, the migration backs off automatically, waits and tries again — settling at whatever your store actually allows. That is why an order migration can take considerably longer than a product migration of the same size, and why more servers or more workers do not speed it up: the limit is on Shopify’s side, per store.

Everything else — products, collections, customers, coupons and content — is paced against your WooCommerce store instead, because those are the phases that read heavily from it: a single product means reading its variations and image details as well. So they run considerably wider than orders, but they are limited by what your WordPress host can serve rather than by anything on the Shopify side.

10. Plans and limits

PlanProductsCustomersOrdersMetafields, coupons, blogs & reviews
Starter — free1010
Basic — $19/mo1,0001,0001,000Yes
Enterprise — $149/mo10,00010,00010,000Yes
Pro — $200/moUnlimitedUnlimitedUnlimitedYes

Limits are enforced in three places so they cannot be worked around by accident:

Upgrading takes effect immediately — start a new migration and the higher limit applies.

11. Progress, logs and notifications

Counters are derived from the mapping table rather than incremented as work happens, so the numbers you see are always the truth even if a batch was replayed.

12. Retrying failed items

A finished migration with failures shows every failed record with the exact message Shopify returned. Retry failed items re-fetches just those records from WooCommerce and re-migrates them — it does not re-run the whole migration.

Records that no longer exist in WooCommerce are marked skipped rather than retried forever. Fix the underlying problem first — that is usually a plan upgrade, a reachable image URL, or a corrected customer email — then retry.

13. API reference

The app's API is consumed by its own embedded frontend and authenticated with a Shopify session token. It is documented here for reference and for debugging; it is not a public integration API, and it is not versioned.

MethodEndpointPurpose
GET/api/shopCurrent shop, granted scopes, whether re-authorisation is needed
POST/api/woocommerce/connectSave and test WooCommerce credentials
POST/api/woocommerce/testRe-test the stored connection
GET/api/woocommerce/previewAvailable counts per entity, plus what your plan allows
GET/api/migrationsMigration history (paginated)
POST/api/migrationsCreate and enqueue a migration
GET/api/migrations/{id}Status and per-entity progress
GET/api/migrations/{id}/itemsMigrated items, filterable by entity and status
GET/api/migrations/{id}/logsActivity log
GET/api/migrations/{id}/downloadPlain-text report
POST/api/migrations/{id}/cancelCancel a running migration
POST/api/migrations/{id}/retry-failedRetry only the failed items
GET/api/billingPlans and the shop's current plan
GET/POST/api/support/ticketsList or open support tickets
POST/api/support/tickets/{id}/replyReply to your own ticket

Migration status shape

{
  "id": 102,
  "status": "processing",
  "entities": [
    { "type": "product",  "total": 1250, "processed": 840,
      "successful": 820, "failed": 20, "percentage": 67.2 },
    { "type": "category", "total": 35,   "processed": 35,
      "successful": 35,  "failed": 0,  "percentage": 100 }
  ],
  "started_at": "2026-08-08T10:30:00Z",
  "completed_at": null
}

Statuses are pending, processing, completed, completed_with_errors, failed, cancelled and paused.

14. Security model

See the privacy policy for what is stored and for how long.

15. Troubleshooting

SymptomLikely cause and fix
"Test connection" fails The REST API is not reachable. Check the store is public over HTTPS, not in maintenance mode, and that no security plugin is blocking API requests. Confirm the key has Read permission and has not been revoked.
Migration is slow, stalls, or many records fail with timeouts Check your WordPress security plugin or firewall first. A migration reads your whole store through the WooCommerce API — thousands of requests from one place over several hours — and security tooling often treats that as an attack and starts throttling or blocking it. Common culprits are security plugins (Wordfence, Sucuri, SolidWP/iThemes, All In One WP Security), rate limiting built into your hosting, a WAF such as Cloudflare, and bot protection that challenges automated requests. Ask your host, or look in your security plugin, for blocked requests to /wp-json/, and allowlist them for as long as the migration runs. Put the protection back to normal afterwards — there is no need to leave your store unprotected.
You are emailed "New order" for every migrated order That is Shopify's own staff notification, not something this app sends, and there is no API an app can use to switch it off. Your customers are not emailed — order confirmations are suppressed on every order we create. Before migrating orders, go to Shopify admin → Settings → Notifications → Staff notifications and turn off "New order", then turn it back on when the migration finishes.
Orders migrate much more slowly than products Expected, and it is Shopify rather than the app. Order creation is rate limited separately from the ordinary API budget, so an order migration paces itself against that limit however many workers are available — a catalogue of products can run several times wider than a batch of orders. How many orders run in parallel depends on your Shopify plan (see section 9): Basic and Grow are the most conservative, Plus and Enterprise run several times wider. The estimated time remaining measures each kind of record on its own, so it will lengthen when a run moves from products onto orders. Nothing is lost by leaving it running.
Product reviews cannot be selected Reviews are stored on the products they belong to, so Products has to be part of the same migration. Select Products — or, if your plan does not include products, upgrade before migrating reviews.
Products migrate without images Shopify downloads images from your URLs. If they 404 — a moved or deleted uploads folder, or hotlink protection — the product is migrated without them and a warning is logged. Make the image URLs publicly fetchable and re-run.
Customers or orders all fail with an access error Customer and order data are protected by Shopify and need approval before an app can write them. Until that approval is in place, products and categories still migrate normally.
"Phone is invalid" The number could not be converted to international format, usually because the billing country is missing. The customer is still migrated, without the phone number.
"Email has already been taken" A Shopify customer with that email already exists. Existing customers are not overwritten.
Far fewer records than expected A plan limit. The wizard and the activity log both state the cap that was applied. Upgrade and run again — already-migrated records are updated, not duplicated.
Progress seems to pause Normal on large products: each one can involve several API calls, and Shopify throttling adds deliberate waiting. The activity log will show it is still working.
Order totals look right but products are not linked Expected — order line items are custom lines. See the note in section 5.

If none of these fit, download the migration report and send it to support. It contains everything we need.

16. Glossary

BatchOne page of source records, processed as a single unit of work
EntityA type of record: product, category, customer or order
MappingThe stored link between a WooCommerce record and the Shopify record created from it
IdempotentSafe to run more than once — the second run produces no extra records
Field sheddingDropping an optional field Shopify rejected so the record itself still migrates
UpsertUpdate the record if it exists, create it if it does not
E.164The international phone format Shopify requires, e.g. +923001234567