The Reluctant Guide to Shopify Migrations

Act II: The Antechamber

The Decision That Costs You Weeks Instead Of An Afternoon

A team modeled color as a variant.

Reasonable choice. Defensible. The admin looked clean. Three colors of olive oil bottle—clear, green, frosted—each as a variant of the same product, with a single SKU pattern and a single PDP. The merchandising team approved it in week three. The engineers nodded. Everyone moved on.

Two months later, the wishlist app went live. It attached to the product, not the variant. (Most wishlist apps attach to the product. The vendor pages don’t always say so. The vendor pages say “save your favorites.” They do not say “save your favorites, modulo a category-theoretic subtlety we are about to discover in your week ten.”) What landed in customers’ wishlists was the product, with no color attached. The whole merchandising layer that had been built on top of color-as-variant—the bestseller widget, the “you wishlisted this” personalized PDP, the abandoned-wishlist email that hero-imaged the wrong bottle—quietly stopped meaning what the team thought it meant.

The fix was not a migration script. The fix was a full catalog reimport. Every product in the catalog had to be restructured, the variant-level color reattached as a product-level distinction, the SKUs renumbered, the URLs reconciled. The frontend development that had piled on top of the old model was, for two months, the wrong frontend development. Rolling back the schema didn’t roll back a decision. It rolled back a season.

A glass bottle displayed neatly on a wall-mounted shelf, while a wide crack splits the wall behind it from top to bottom, with debris on the floor beneath.
The admin looked clean. The wall was another matter.

The team was not careless. The choice they made—color as variant—was the choice a thoughtful merchandiser would make on most platforms. It was the choice the platform’s admin actively encouraged them to make. The trouble was that one downstream tool, the wishlist app, attached to the wrong level of the catalog hierarchy, and there was no mechanism on Shopify by which you could discover this fact short of the wishlist app going live and a customer using it.

On a custom platform, a bad schema decision costs you an afternoon. On Shopify, it costs you weeks.


A Shopify replatform is a project that rewards iteration almost everywhere. You will get the export format wrong on the first pass and refine it. You will get the delta sync logic wrong and refine it. You will discover validation rules halfway through and bolt them on. You will swap your import tooling once, maybe twice. You will scope features in waves. Most of this is fine. Most of this is the project working as designed.

There is exactly one phase that does not work this way.

Data modeling—specifically, the three decisions we are about to walk through—must be locked before a line of code is written, because the cost of changing them after code has been written is not a sprint, it is a season. Teams that treat the schema like another iteration cycle end up paying for the assumption. The assumption is that Shopify’s data model behaves like the data model of the platform they are leaving. It does not. The trouble is structural, and it is worth slowing down for.

Shopify doesn’t expose a raw database. You cannot drop a table, fix the schema, and reimport. You cannot truncate a metafield definition and rerun the seed. Metafields and metaobjects accumulate; rolling them back means updating every record that references them, one record at a time, against an API. Collection definitions, once products are organized against them, are painful to restructure—not impossible, but painful in the specific way that means a senior engineer spends a week on it and nobody is happy at the end.

The Plus store you are developing in is, in nine projects out of ten, the same Plus store that was activated when the deal was signed. You cannot wipe it. You cannot reset it. There is no --force flag. You can ask Shopify support to help, and they will, and the conversation is not pleasant. (Ask us how we know.)

A bad modeling decision doesn’t stay local. It propagates. Fix the variant model in week eight and you’ve also broken the collection logic and the metafield mappings and the import pipeline. Fix the metafield schema in week ten and the frontend devs are now staring at a query that returns null for half the fields the design depends on. Fix the collection logic in week twelve and the merchandising team is rebuilding three months of curation by hand. The schema is load-bearing in a way that the rest of the project is not. Treat it accordingly.


The first of three decisions is the product-versus-variant model.

Volume is no longer the constraint it used to be. Shopify now supports 2,048 variants per product, which sounds like a number designed by a committee but which is, for almost any catalog, more than enough. The constraint that matters is not how many variants you can have. The constraint that matters is what anchors to the product versus what anchors to the variant.

Three option types per product. That is the structural cap, and it has not moved. Color, size, and one more thing. If your current platform models color, size, fabric, and finish, you are about to have a conversation with the merchandising team that they will not enjoy. Pick three.

Discount rules attach at the product level. Tax exclusions, the same. Most third-party apps, the same—the wishlist app, the bundling app, the personalization layer, the loyalty engine. They save the product, not the selected variant. If you have modeled colors as variants and a customer wishlists a product, what you have captured is a customer who likes the product. You do not know which color. You have an ambiguous intent and no way to act on it. The personalized email goes out with the hero image of the default variant. The default variant is the one the merchandising team picked because it sorted alphabetically. You have, accidentally, marketed clear olive oil to a customer who explicitly chose green.

Themes cap variant display at 250. Above that, you are writing custom storefront logic to render the picker—which is fine, except that it is a workstream nobody scoped, because volume is not supposed to be the constraint.

The decision is not technical. It is editorial. Which axis of differentiation should the platform treat as the same product, and which should it treat as separate? Merchandising has a view. Design has a view. Engineering has the constraint sheet. We have seen this conversation go badly when one of those three is absent from the room, and we have seen it go very badly when the conversation happens by email. Put them in the room. Do not skip it.


The second is the metafield and metaobject schema.

Metafields and metaobjects are the shape of every custom attribute in your store. Country of origin on a product. Pairing notes on a wine. Gift-set composition on a bundle. Loyalty tier on a customer. Anything Shopify does not model natively is modeled here. Every record you import will be structured against this schema. The schema, therefore, must exist in Shopify before the first import runs.

This is not how teams build it.

The way teams build it is incrementally. An engineer adds a metafield because a component needs one. A designer asks for a structured Pairing notes block, and a metaobject appears to support it. A new feature lands, and three more fields appear. By month three, the schema is a patchwork. The schema was never designed—it accreted. The schema accretes because there is no week when anyone was asked to design it. Adding a metafield is a five-minute admin action; designing a schema is a workshop. The default is the five-minute action.

Then the filter logic arrives. The storefront needs to filter products by a combination of attributes—color and price range and country of origin and “currently on promotion”—and the schema was not designed with filter behavior in mind. The constraints, it turns out, run the other way. What Shopify’s filter engine supports should shape how you model the data. Not the reverse. The product team did not know this in week three. The engineering team learned it in week ten, when the filter wouldn’t.

(There is a tool. It is called shopify_toolkit. We wrote it. It maps and validates the schema before anything moves. Use it. Or don’t, but use something—do not run the first import against a schema you have not formally audited. We have seen the version of this where someone wings it and it costs them a quarter. They were good engineers. They were not, in that quarter, lucky ones.)


The third is collection logic.

This is the one that catches the most teams, because Shopify’s smart collections look like the answer and aren’t.

The structure is flat. There is no parent-child hierarchy on Shopify collections. If your incumbent platform organized your catalog in a tree—Pantry > Oils > Olive Oils > Single-Origin > Tuscan—that tree does not migrate. You are not bringing it across. You are building an equivalent on the other side, with the tools the new platform actually provides. The equivalent will look different. The merchandising team will need to make peace with this. The conversation in which they make peace with this should happen in week two, not week ten, because if it happens in week ten, the URLs are already wrong.

Smart collections look like the answer. The pitch is elegant: define a rule, and the collection populates itself. Color is green AND price is over fifteen dollars AND tag contains “holiday”. In practice, the condition logic is severely limited. No nested AND/OR. No NOT for most conditions. The boolean algebra you use to organize your real catalog probably cannot be replicated in the smart collection rule engine. Try it. Discover this on day one rather than day fifty.

Two paths actually work.

The first is purpose-built metafields plus a custom app to maintain them. You design the metafields specifically to satisfy the rules smart collections can handle, and you write an app that keeps those metafields in sync as products change. The catch is that Shopify Flow has no product update trigger—you cannot fire the sync from a no-code automation—so the app is real engineering work. Worth it for the right catalog. Not for every catalog.

The second is to let the PIM own the logic entirely. The PIM holds the rules. The PIM runs the rules. The PIM pushes product IDs to Shopify as manual collections, in order, on a schedule. Shopify becomes the rendering layer, not the logic layer. This is the cleaner architecture for any catalog of meaningful complexity, and it is the one we recommend more often than not.

Shopify becomes the rendering layer, not the logic layer. Hold that sentence. It is going to come back, in this book, in several different costumes.


Greycott & Co. did this exercise in week two. The room held three people: Owen Caldwell, head of e-commerce; Priya Shah, director of operations; and Daniel Okafor, the lead engineer who had spent the previous week building a spreadsheet that nobody had asked for but that turned out to be the thing that anchored the conversation. (The full backstory of Daniel’s spreadsheets is a recurring shape in this book. He keeps building them. The room keeps not asking for them. The room keeps needing them.)

Greycott has nine product types. Olive oil. Vinegar. Spice blends. Gift sets. Salts. Honeys. Sauces. Pantry bundles. And one ambiguous category that the team variously calls “specialty” or “monthly” or “the box program,” depending on who is talking.

Three of those product types have color as a variant—bottle color on the oils, jar color on the honeys, packaging color on the salts. Two of them absolutely do not—spice blends and vinegars come in one color each, and the team has never thought of them as having a color axis at all. One of them depends on whether you ask Priya or Owen.

The one in dispute is gift sets.

Owen, who runs e-commerce, thinks of a gift set as a single product with packaging variants. The “Holiday Pantry Trio” comes in three giftwrap colors—red, green, kraft—and they should all live on one PDP, with the customer picking the color at checkout. He is thinking about how it appears to the customer, about consolidation of reviews, about the click-through math on a hero placement. One PDP, three variants. This is the e-commerce view.

Priya, who runs operations, thinks of those same three giftwrap colors as three different products. Different SKUs, different inventory pools, different packing slips, different reorder thresholds, different photography in the wholesale catalog. The wholesale team treats them as separate. The 3PL picks them as separate. The QuickBooks reconciliation, please, treats them as separate. Three products, no variants. This is the operations view.

Neither view is wrong. Both views are coherent inside their own frame. The choice between them has consequences that propagate in opposite directions: choose Owen’s model and the wholesale picking workflow needs custom logic; choose Priya’s model and the storefront merchandising loses the consolidated PDP and gains three nearly-identical product pages that the SEO team will spend the next quarter trying to deduplicate.

Two pairs of hands each gripping the same plain cardboard box from opposite angles—one facing the presentable gift-front, the other facing the utilitarian seamed back.
Same box. Two correct answers. One Shopify data model.

We are not going to tell you which way the conversation went, because the specific answer is less interesting than the fact that it happened in week two. The room had Owen, Priya, and Daniel. Daniel said almost nothing for the first hour and then, near the end, said something useful about which of the two models the wishlist app would behave well against. The decision got made. Daniel updated his spreadsheet. The data import was structured against the decision. The frontend devs, three weeks later, were building against a model the room had agreed on.

The requirement you inherit is not the requirement you have. The requirement you inherit is the way the old platform happened to model gift sets, which is not a requirement at all; it is a residue. Greycott’s Magento catalog modeled gift sets as variants, because in 2018 a contractor decided that was easier. The 2018 contractor was not in the room in week two. He should not have been. The room in week two was a room of people who had to live with the consequence. The room got to redecide.

Data modeling rewards waterfall discipline in exactly this shape. The room exists. The room produces a decision. The decision is captured in writing. The development that follows is structured against it. No iteration. No discovery sprint. No “we’ll see how it shakes out.” If the room cannot produce a decision, the project does not move to the next phase. (If the room can produce a decision but Owen and Priya are not in it, the room has produced the wrong decision. We have done this badly enough times to be confident on this point.)


Most of what is left in a replatform is iteration-friendly. Your first export from the source system will be wrong. The columns will not line up with the Shopify schema. The encoding will be off in a way you do not discover until a product description renders with a smart quote turned into three characters. You will refine the export. You will refine it again. You will, near launch, refine it for the last time. This is fine. This is what export iteration is for.

Your delta sync logic—how records created on the old platform during the migration window flow into Shopify—develops as you understand the data. The validation rules develop. The transformation logic develops. Your import tooling itself is a decision you can revisit. Matrixify is, for most one-off imports where you control the export format and you do not need multi-language support, the right call: a usable UI, no extra infrastructure, good enough. For recurring syncs, complex transformation, or stores with several markets, the Bulk Operations API gives you sequencing control that Matrixify does not—you know exactly when each operation completes before you queue the next, and you can build conditional logic into the pipeline. Matrixify also wants broad store permissions, which is a non-starter for some security-conscious enterprise clients. We have switched tools mid-project. It was fine.

The rhythm that works: run the first import early in the project to surface surprises. Iterate on export and import as the project develops. Run a clean reimport before launch. That loop handles a lot—as long as the data model underneath it is stable.

The data model is the thing the loop cannot fix.


Before the first import runs—before any frontend dev depends on a schema, before any merchandising widget is configured, before the steering committee asks for a demo—every team should be able to answer four questions.

Is the product-versus-variant model agreed, in writing, and reviewed against the platform’s native features—discounts, taxes, wishlist, theme rendering caps?

Are the metafields and metaobjects defined in Shopify, not just documented in a spreadsheet that someone keeps meaning to clean up?

Is the collection strategy decided—smart collections, custom metafield-driven app, or PIM-driven manual collections—with an explicit acknowledgment of which logic Shopify will and will not own?

Is the import tooling decided—Matrixify for the one-off case, Bulk Operations API for anything more durable?

Teams that can answer all four before the first line of code have done the hard work. Teams that defer one of these answers will find it again—at the worst possible time. The worst possible time is week ten, when the wishlist app goes live, and the merchandising team’s hero PDP shows the wrong color. (The worst possible time is week twelve, when the discount engine fires at the wrong level. The worst possible time is week sixteen, when the filter behavior does not match the schema. The worst possible time is the time you discover the thing, which is always too late to fix cheaply.)

The conversation you have early or you have late. Same conversation. Different cost. The early conversation costs you a week of workshops and a slightly delayed start. The late conversation costs you a season.

Two hourglasses on a plain table, one small with a few grains of sand remaining and one large with a vast heap of fallen sand, both mid-completion.
Same conversation. One costs a week. The other costs a season.

Not waterfall everywhere. Waterfall here. Data modeling is the part of a Shopify migration that earns its planning discipline by punishing the project that did not plan.


Here lies the wishlist app.

It captured every product. Just none of the colors.