Documentation

Everything Store 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

Store 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 Store 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, tags
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.

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. Store 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, Store 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, Store 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, Store 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.

10. Plans and limits

PlanProductsCustomersOrdersMetafields
Free1010
Basic — $19/mo2,0002,0001,000Yes
Pro — $49/mo20,00020,00020,000Yes
Enterprise — $149/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.
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