Shopify to WooCommerce Migration: What Has No Import Path

A customer was given a $75 gift card for your Shopify store last December. In March she opens the email, copies the sixteen characters, pastes them into the checkout of what is now a WooCommerce store, and the checkout tells her the code does not exist. She is right to be annoyed. That $75 was paid for, it sits on somebody's books as money the store owes, and nothing in the Shopify to WooCommerce migration moved it.

Nobody made a mistake that a better importer would have caught. Shopify deliberately keeps gift card codes out of its own exports, and WooCommerce's official gift card extension refuses codes it did not generate. Metafields and app data fail in quieter ways, and the quiet is what makes them expensive: the product pages look complete until somebody notices the size chart has gone.

Verified against Shopify and WooCommerce documentation on 22 September 2026. The sources for each claim are linked where the claim is made. Both vendors revise these pages without notice.

Shopify gift card export: the code column is called Last Characters

Go to Products, then Gift cards, then Export, and the CSV that comes back is a careful record of everything about a gift card except the one field that makes it spendable. Shopify's managing created gift cards page is plain about it:

For security, the standard CSV export includes only the last 4 characters of each gift card code. The standard CSV export can't be used to recreate cards or migrate them to another system on its own.

Two sentences later: "Ongoing or recurring full-code export isn't supported. If you're migrating gift cards to another system, then review that system's gift card import requirements and issue new codes when needed."

The same page explains the reason. Gift cards are treated as currency, so "only the customer can see the full gift card code after it's created," and the full code "can't be viewed in the Shopify admin after the gift card is created." The API follows the same rule. The GiftCard object in the Admin GraphQL API (version 2026-07 when read) has a lastCharacters field and a maskedCode field, and the documentation for the second one says "Everything but the final four characters is masked." The object's field list has no unmasked code field, so there is nothing to request with a broader access scope.

The wording "ongoing or recurring" leaves a small opening, and it costs nothing to ask Shopify support in writing whether a one-time full export is available for a migration. Plan as though the answer is no. Every public statement from Shopify points that way, and a plan that depends on an exception will stall two weeks before cutover.

What the export does give you is enough to rebuild the liability, which is what matters. These are the columns worth keeping:

Column Why it matters for reissuing
Id Shopify's identifier for the card (not the code). Your cross-reference back to the old record.
Last Characters The only piece of the code you have. Useful when a customer writes in quoting their old card.
Customer Name, Email Who bought it.
Recipient Name, Recipient Email Who it was bought for. Often a different person, and the one who holds the code.
Send At When the card was or will be delivered. A future date means the recipient has not seen it yet.
Expires On Blank if it never expires.
Initial Balance, Current Balance, Currency The money. Current Balance is what you owe.
Expired?, Activated?, Deactivated At Which rows still need reissuing.
Issue method Issued by staff, purchased, or generated by an app.

One more mechanic from the same page. Export 50 cards or fewer and the browser downloads the file. Export more than 50 and it arrives by email, with a copy going to the store owner if someone else ran it. If the person doing the migration is not the owner, agree in advance who is watching which inbox.

WooCommerce would not accept the old codes anyway

Suppose the codes did come out. WooCommerce core has no gift card feature, so the destination is an extension, and the official one closes the door from its side. Gift Cards for WooCommerce is listed on the WooCommerce marketplace at $79 for a one-year plan (price shown on 22 September 2026), and its store owner's guide states:

For security reasons, Gift Cards only supports its own 19-digit code format (XXXX-XXXX-XXXX-XXXX).

A little further down: "it is not possible to import gift card codes that follow a different format (for example, codes created with other plugins)." The workaround the guide suggests is the same one Shopify suggests from the other end: create new codes and tell existing holders their old codes have changed. The two vendors agree on the answer without ever mentioning each other.

The extension's importer does the useful half of the job. A CSV needs at least four columns (Code, Recipient, Sender and Balance), and the Code cell can be left empty so the plugin generates one. A Delivery date column makes the extension email each new code on that date, as long as the date sits at least a few minutes ahead of the store's clock. The guide also warns that the importer cannot sync codes between two WooCommerce sites or update codes that already exist, so you get one clean run rather than a draft and a correction.

Third-party gift card plugins for WooCommerce exist, and some may accept arbitrary code formats. Read the plugin's own import documentation before you rely on that. And it still leaves you with codes you do not have.

The same logic holds one layer down, in payments. Saved cards and subscription billing tokens move between processors under their own rules, and never through a CSV you hold. That is covered in what happens to stored cards when you switch processors. Plan the gift card reissue and the token move as two separate jobs. They are.

Reissuing gift cards without losing anyone's balance

Reissuing is where stores lose money or goodwill, and mostly through the order of operations rather than the arithmetic.

Freeze first, export second. Any Shopify order taken after the export can still spend a gift card, which makes your Current Balance column stale. Export after the last order Shopify will ever take, not the week before.

Filter the rows. Keep cards where Activated? is yes, Expired? is no, and Current Balance is above zero. Sum Current Balance by Currency and write the totals down before you touch anything else. Say the filtered file comes to 212 cards and $6,480.00 (example figures). The WooCommerce side should add up to exactly that after import, and checking one total is quicker than checking 212 rows.

Decide who receives the new code. Where Recipient Email is filled in, the recipient holds the card and should get the replacement. Where it is blank, the purchaser bought it for themselves or passed the code on by hand, and the purchaser is the only contact you have. Write that rule down before building the import file, because it decides the Recipient column for every row.

Treat future Send At dates separately. A card with a Send At date after your cutover was paid for but has not been delivered yet. The recipient does not know it exists. Put that same date in the Delivery date column and WooCommerce will deliver the new code on schedule, which is what the buyer paid for. Send the buyer a note that the store has moved.

Carry expiry dates across honestly. If a Shopify card expires on 1 March 2027, the new card should expire no earlier. Gift card expiry is also regulated in many places, so check your own jurisdiction's rules before you shorten anything. That question belongs to a lawyer, not to this page.

Store credit is a separate liability. Shopify's store credit lives on customer accounts, not on gift cards, so it will not appear in the gift card export at all. Shopify's store credit help page describes viewing a balance and its history on each customer's profile. I did not find an export described on that page. The Admin API exposes a StoreCreditAccount object with a balance, behind the read_store_credit_accounts scope. A store with a few dozen credited customers can copy balances by hand. More than that justifies a short script, and the approach in pulling a full dump through the vendor's API applies unchanged.

Say it in the email. The message that goes out with each new code should give the old last four characters and the balance carried over, so the customer can match it to the card they remember. It should also say plainly that the old code will stop working. Once the Shopify checkout closes, the old code has nowhere left to be redeemed.

Metafields: what the product CSV carries and what it drops

Metafields are where a Shopify store keeps the things its theme was built around: fabric, care instructions, size charts, ingredient lists, a "pairs well with" product, a downloadable manual. Whether each one leaves with the store depends on which object it is attached to, whether it has a definition, and what type it is.

Shopify's product CSV page says that "After a product metafield is defined, it's included in your product CSV exports," under a header such as Fabric (product.metafields.shopify.fabric). The next paragraph is the one that catches people: "Variant metafields aren't supported for product CSV import/export." If the per-size measurements live on variants, the product CSV simply does not contain them.

Customers are narrower still. The customer CSV page lists exactly six metafield types that can be exported: boolean, date, date and time, integer, decimal, and single line text. A multi-line note or a JSON blob attached to customers is not on that list. Nor is a reference to another record. Fetching those means going to the API.

Then there is the problem of what the values look like when they do arrive. Shopify's list of metafield data types gives the stored form of each reference type:

  • product_reference is stored as gid://shopify/Product/1
  • metaobject_reference is stored as gid://shopify/Metaobject/123
  • file_reference is stored as gid://shopify/MediaImage/123
  • rich_text_field is stored as a JSON tree starting with {"type":"root","children":[...]}

Now picture a "Size guide" metafield that points to a metaobject. The CSV cell says gid://shopify/Metaobject/123. The size guide itself (the rows, the measurements, the heading) lives in the metaobject, which is not in the product file. After the store closes, that ID points at nothing. It is the same failure described in why integrations break when record IDs change, where a value that was really a pointer arrives looking like data.

WooCommerce's side of the handshake is simple, but you have to do it by hand. Its product CSV importer documentation says the built-in importer "maps known product fields and columns prefixed with meta:." So a Shopify header of Fabric (product.metafields.shopify.fabric) has to be renamed to something like meta:fabric before import, or the column is skipped. After renaming, the text lands as post meta on the product. Whether shoppers ever see it depends on the WooCommerce theme, which knows nothing about a key called fabric unless somebody builds that in.

The experimental WooCommerce tool takes a different route. I read the products class of migrator-cli on 22 September 2026 (the repository's last push was in December 2025). It asks Shopify for metafields(first: 100) on each product, then writes every one into a single meta entry named _migration_data, keyed by namespace and key joined with an underscore. Up to the first 100 per product survive that way. But nothing can be displayed until somebody unpacks that array into fields a theme can read. The one exception it handles is SEO: the global title_tag and description_tag metafields are copied into Yoast's title and description meta.

Before choosing a method, list what you actually have. In the Shopify admin, Settings then Custom data shows every definition per object type. For each one, write down the object (product, variant, customer, order, collection), the type, and whether the storefront displays it. A text field on products is a column rename. A variant metafield or a metaobject reference is a small data project, and it is better to find that out before signing a fixed-price migration quote.

App data was never in Shopify's export

The third category is the least visible, because it was never inside the store's own data to begin with. Reviews, loyalty points, wishlists, subscription contracts, back-in-stock requests, product option builders: each app stores its data where its developer chose, and Shopify's developer documentation on metafield ownership describes three possible places.

First, the app's own servers. Shopify's guidance to developers is that "Generally, private app data should be stored in an app-specific, secure database." None of that reaches any Shopify export, because it was never in Shopify.

Second, app-owned metafields, which use the reserved $app namespace. The app controls both the structure and the values. The merchant can view them in the admin by default but not edit them.

Third, app-data metafields, which are "tied to a specific app installation and are completely hidden from the Shopify admin." Per the same page, "only your app can access its own installation's metafields." Not the merchant, not another app, not a migration tool with read access to products.

So the export for app data is whatever the app itself offers, and you have to check each one. Go to the list of installed apps and open each app's settings or help pages. Look for an export and note the format, and note what it leaves out (review photos, reply threads and points history are the usual gaps). The tools on the WooCommerce side have their own import formats, and the column mapping between the two is the real work.

One clock matters more than any other here. Shopify's privacy compliance documentation says that "48 hours after a store owner uninstalls your app, Shopify sends a payload on the shop/redact topic" so the app can "erase data for that store from your database." People uninstall apps when they tidy up a store they are leaving, and that tidying starts the countdown. Export from every app first. Uninstall afterwards, or leave the apps alone until the store closes.

Subscriptions deserve a sentence of their own. migrator-cli's README includes a skio_subscriptions command that reads JSON exported from one specific subscription app's dashboard. It is a useful hint about the scale of the problem: even WooCommerce's own tool treats each subscription app as a separate migration, with the payment tokens handled on a separate track again.

What has a path, and what you rebuild

This table covers the three items above plus the things usually quoted alongside them. "Path" means a documented way to move the data, not a guarantee that it arrives fit for use.

Item Out of Shopify Into WooCommerce What you actually do
Gift card codes Last 4 characters only Official extension accepts its own format only Issue new codes for Current Balance and email holders
Gift card balances CSV, full detail Gift Cards extension CSV import Filter, total by currency, import, compare totals
Store credit Per customer profile, or the API No equivalent in core Reissue as gift cards or credit, decided before export
Product metafields (defined) Product CSV meta: columns Rename headers, then have the theme display them
Variant metafields Not in product CSV meta: on variation rows Pull through the API
Reference metafields IDs such as gid://shopify/Metaobject/123 Text, meaningless Resolve each reference to its content before export
Customer metafields Six simple types only Depends on the customer import method API for everything else
App data The app's own export, if one exists The destination plugin's import, if one exists Export per app before uninstalling anything

Which order you do this in matters more than any single tool. Gift cards come last, because their balances change until the final Shopify order. App exports come first, because uninstalling starts a 48-hour deletion clock. Metafields go in the middle, as soon as the WooCommerce theme is settled enough to know which fields it will display. That tells you which fields are worth resolving and which can go into an archive and stay there.

Frequently asked questions

Can I migrate Shopify gift cards to WooCommerce with the same codes?

Not with anything Shopify documents. The gift card CSV export includes only the last four characters of each code, the Admin API returns a maskedCode with everything but the final four characters hidden, and Shopify's help page says the full code can't be viewed in the admin after the card is created. On the receiving side, the official Gift Cards for WooCommerce extension only accepts its own 19-character XXXX-XXXX-XXXX-XXXX format and can't import codes created by other systems. The workable route is to issue new codes on WooCommerce for the outstanding balances and email each holder their replacement.

Do Shopify metafields export in the product CSV?

Product metafields do once they have a definition. Shopify's product CSV page says a defined product metafield is included in exports under a header such as Fabric (product.metafields.shopify.fabric). Variant metafields are not supported in product CSV import or export, and the customer CSV exports only six simple metafield types. Reference metafields come out as Shopify IDs like gid://shopify/Metaobject/123, which mean nothing to WordPress.

What happens to my Shopify app data when I uninstall the app?

Shopify's developer documentation says that 48 hours after a store owner uninstalls an app, Shopify sends that app a shop/redact request so it can erase the store's data from its own database. Much app data never lived in Shopify at all, and app-data metafields are hidden from the Shopify admin and readable only by the app that owns them. Export from each app's own screens before you uninstall anything.

Is there an official Shopify to WooCommerce migration tool?

WooCommerce publishes an experimental WP-CLI tool on GitHub called migrator-cli, with commands for products, orders, coupons and a few other things. Its README and file list, read on 22 September 2026, include no gift card command. The older Shopify Migration for WooCommerce extension is marked on WooCommerce.com as no longer available, with its documentation left online for existing users.