Store migrations lose data because WooCommerce, Shopify and Haravan describe the same shop in different shapes: order status, addresses, product options, passwords and URLs do not map one to one. Vibery Sites is designing a universal data model and a dry-run plan that names every loss before anything is written.
We are building this. It is not available yet. Nothing below has been proven in production, and there is no release date. See what is live and what is rolling out.
Updated 2 November 2026: platform limits re-checked on test stores (options, order dates, order emails, refunds, page sizes) and a new section on how blog posts and articles move.
Key takeaways
- Each platform stores orders, addresses, variants and redirects differently, so a plain export and import silently changes meaning.
- Writing one converter per pair of platforms does not scale: ten platforms would need 90 converters, while a hub design needs 20 adapters.
- Anything the destination cannot hold is listed in a dry-run plan before the first write, never dropped silently.
- The source store is never written to during a migration, and a rollback is possible until cutover.
- This capability is in design. Where we have not verified a platform behavior, this post says so.
Why does moving a store between platforms lose data?
Because each platform models the same shop differently, so copying fields does not copy meaning.
A paid order can carry two separate statuses, one for money and one for shipping, on one platform and a single combined status on another. An address can be a set of administrative codes in one place and free text in another. A product can have as many options as you like in one system and three at most in another. A file export hides all of this until the new store looks wrong.
The usual fix is to write a converter for every pair of platforms. Every converter then hides its own private decisions about what to drop, and every new platform multiplies the pile.
How does a universal data model avoid N² converters?
A universal data model is a shared shape in the middle: each platform gets one reader into it and one writer out of it, so N platforms need 2N adapters instead of N×(N−1) converters.

Three rules keep the hub honest:
- The platform stays the store of record. The universal model is an interchange shape, never a second database of your shop.
- Each record keeps a platform-specific bag. It holds what the universal model has no field for, so WooCommerce to the model and back to WooCommerce is designed to be lossless.
- Cross-platform losses are reported in a dry-run plan. They are never silent.
Source ids are kept as strings, because they are a Shopify global id on one platform and an integer on the others. Money is a decimal plus a currency, never a floating-point number.
How does an order status map between WooCommerce and Shopify or Haravan?
WooCommerce has one status per order, while Shopify and Haravan use a pair of financial status and fulfillment status, so we use the pair plus a closed flag.
A pair collapses into one value with less loss than one value splits into two. Our draft mapping from WooCommerce:
WooCommerce | Financial | Fulfillment | Closed |
|---|---|---|---|
pending | pending | unfulfilled | no |
on-hold | authorized | unfulfilled | no |
processing | paid | unfulfilled | no |
completed | paid | fulfilled | yes |
cancelled | voided | cancelled | yes |
refunded | refunded | any | yes |
failed | voided | unfulfilled | yes |

failed has no native twin on the other side, so it lands as voided and the original value is stored alongside. Going back, the first matching row is used, but on a WooCommerce to WooCommerce trip the stored original status wins. That one rule is the whole lossless story for order status. Counting the same number of orders does not prove the statuses are right.
How are Vietnamese addresses handled?
Haravan stores province, district and ward as administrative codes, while WooCommerce and Shopify mostly store free text, so the model keeps both a code and a name for each level.
From Haravan we fill code and name. From WooCommerce or Shopify we can fill only the name. Loading into Haravan then needs a step that resolves names to codes, and anything that does not resolve goes into the plan as a drop or a manual fix. We do not guess a ward.
What differs in products, options and variants?
Haravan and Shopify each allow at most three options per product, and Shopify allows at most 2048 variants per product, so a WooCommerce product with four variation attributes cannot be expressed on either.
WooCommerce has no fixed cap on attributes. Haravan does not refuse a fourth option: its API answers success and silently drops the option. Our plan step catches any product with more than three options before anything is written. The model keeps options in order, each with its values and a flag for whether it drives variants, so a WooCommerce attribute like "material" comes back as a plain attribute rather than an invented variant.
Each adapter declares its limits, such as maximum options and whether it supports redirects or blogs. The plan reads them and reports the product that does not fit before loading, not as an error at product 4,000. WooCommerce has a single stock location, which maps to one inventory level. A multi-location source loaded into it will be reported as a collapse.
What cannot be migrated at all?
Customer passwords cannot be written to a destination, and in the first version refunds are not written either.
Passwords. The model has no password field by design. Shopify has no password import, and a hash from one platform is not a credential on another. Plan an invite or reset email for customers in your launch communication.
Refunds. In the first version refunds are read-only: extracted and kept for reporting and verification, not recreated. That is a scope choice, not a platform limit: Haravan does have an endpoint for writing refunds.
Order dates and emails. Haravan does not accept backdated order dates: the date you send is ignored and the order is stamped with the time of import. WooCommerce's REST API does not accept them either. Shopify does. Where the destination cannot hold the original date, we keep the original date in the order notes and report it in the plan.
WooCommerce also has a trap in its API: creating an order through REST sends customer emails and reduces stock. Loading 20,000 historical orders that way would email 20,000 customers and empty your inventory. So we plan to import historical orders through our own WordPress plugin, with emails off and stock untouched. Shopify's order creation can be told not to send receipts, and in our test it recorded no email event.
How do URLs, SEO and blogs survive a migration?
Old URLs need a 301 redirect, and WooCommerce core has no redirect API, so loading into WooCommerce needs side-work in our own WordPress plugin.
Shopify and Haravan both support redirects on the platform side. Without a place to store them, a Shopify to WooCommerce move has nowhere to put old /products/foo paths. Verification samples URLs on the destination and expects 200 or 301. SEO fields (title, description, canonical, noindex) travel with products, collections, pages and articles.
Shopify and Haravan have blogs that contain articles; WooCommerce has no blog entity. When the destination is WooCommerce, the blog loads as a category and its articles as posts in it. The plan lists this as a transformation so nobody finds out on launch day.
How do blog posts and articles move?
An article moves as the rendered HTML a visitor sees, with its images re-hosted, its publish date and draft state kept, and a 301 map for the old address. Layout built from WordPress blocks does not survive. We checked each point below on test stores, in both directions between WordPress and each of Haravan and Shopify. The migration feature itself is still in design and not available.
- Body is rendered HTML, not builder markup. WordPress block markup means nothing on Haravan or Shopify, so we send the HTML the visitor sees. Layout built from WordPress blocks is lost. Shortcodes arrive as frozen output or as raw text, so the plan lists the shortcodes of each post as a warning.
- Images are re-hosted on every move. Each image is uploaded to the destination, and image addresses and links between your own articles are rewritten to the new addresses.
- One blog per article, or many categories. Haravan and Shopify put each article in exactly one blog; WordPress allows many categories. Going from WordPress to Haravan or Shopify, the first category becomes the blog and the extra categories become tags. Going the other way, the blog becomes a category.
- Past publish dates and drafts are kept. All three platforms accept a past publish date, and a draft stays a draft.
- SEO title and description are carried. On WordPress this needs the Rank Math SEO plugin active and set up.
- Excerpts lose formatting on WordPress. A WordPress excerpt is plain text, so a formatted summary from Haravan or Shopify arrives without its formatting.
- An old-to-new URL map is always needed. WordPress article links drop the blog part of the address, so /blogs/news/my-post becomes /my-post/. We always build a 301 map from old addresses to new ones.
- Authors are matched by name. Haravan and Shopify store the author as free text, WordPress as a user account. We match the name to an existing WordPress user; if none matches, the article goes to the site's administrator account and the plan reports it. Creating new author accounts is an opt-in setting, off by default.

The plan report lists, per article, the shortcodes, the lost layout and any author that did not match, so nobody finds out on launch day.
How do platform API limits shape the migration?
Each platform's API limits decide how extraction runs, and none of that leaks into the universal model.
Haravan | Shopify | WooCommerce | |
|---|---|---|---|
Rate limit | Leaky bucket: 80 slots, drains 4 per second; 429 with Retry-After | Not pinned down in our spike | Batches up to 100 objects per request |
Bulk read | None; orders come at most 50 per page, and asking for more silently returns 50 | GraphQL Bulk Operations return JSONL | — |
Order history | Paged crawl | Last 60 days unless the app has the read_all_orders scope | — |
So Haravan extraction is a paced, sequential, resumable crawl that can stop and continue without starting over. On Shopify, a naive extract silently misses history older than 60 days unless the app requests read_all_orders, and its offline tokens expire after 1 hour with a 90-day refresh token; both rotate, and refreshing twice at once invalidates your own token. See the Shopify bulk operations guide and the WooCommerce REST API reference.
How would the migration flow work, and how do you roll back?
Four steps: plan, load, verify, cutover. The source store is never written, and you can roll back until cutover.

- Plan. Counts per data type plus the drops report. No writes anywhere.
- Load. In dependency order: media, collections, products with variants, customers, orders, pages and articles, redirects. A record maps each source id to its destination id, so a crashed load resumes and updates instead of duplicating.
- Verify. Count and checksum per data type, plus the URL sample.
- Cutover. Re-read anything changed since the load started and apply it. The store stayed live the whole time; this closes the gap.
Rolling back before cutover means deleting the destination records the migration created. Because that is destructive, it asks for confirmation. The migration's own bookkeeping is Vibery's, tenant-scoped; your commerce data is not copied into a separate Vibery database.
What have we not proven yet?
Nothing here has been proven end to end.
- The first proof will be WooCommerce to the universal model and back to WooCommerce, byte-faithful, before a second platform is touched.
- Shopify access to orders and customers. Reading them needs Shopify's approval for protected customer data, and history older than 60 days needs a further scope. Both are still pending, so Shopify order extraction is not proven.
- Whether the reported drops are the complete set. The plan is only as honest as the capability flags behind it, and those come from reading docs and running spikes, so expect corrections.
How does Vibery Sites fit in?
Vibery Sites is a control plane for WordPress and WooCommerce sites, run from a dashboard or from Claude Code and Codex over MCP. Store migration is being designed as part of it, and it is not available yet.
The bet is simple: if every loss is named before the first write, a migration becomes a checklist instead of a surprise. For what is live today and what is rolling out, see the build log. For related reading, see WooCommerce export and bulk edit speed and the WordPress MCP token benchmark.
FAQ
Can Vibery Sites migrate my Haravan or Shopify store today? No. The capability is in design and has no release date. This post describes the plan, not a shipped feature.
Will customer passwords move to the new store? No. Passwords are never migrated, and Shopify has no password import. Customers will need an invite or reset email.
Will refunds be recreated on the new platform? Not in the first version. Refunds are read and kept for reporting and verification. Haravan does have an endpoint for writing refunds, so writing them is a scope decision we may revisit.
How do you stop a migration from losing SEO? Old URLs get 301 redirects, which WooCommerce needs a plugin endpoint for, and verification samples URLs on the destination expecting 200 or 301.
What happens if the destination cannot hold something? It appears in the dry-run plan as a drop or a manual fix, before anything is written.
Do blog posts keep their layout and old links when moved from WordPress to Haravan or Shopify? No. The body moves as rendered HTML, so block layout is lost and shortcodes are listed as warnings; images are re-hosted and links rewritten. A 301 map is always needed because WordPress links drop the blog part. The feature is in design and not available yet.
Want early access to Vibery Sites? Request a beta invite.


No comments yet