WooCommerce Content Migration: Products, Categories, and Reviews

WooCommerce Content Migration: Products, Categories, and Reviews

WooCommerce runs a huge share of the web's WordPress stores — estimates put its share of all online stores in the 30%+ range — which also means it's the single most common storefront migrations get asked to move off WordPress entirely. Products in WooCommerce aren't a single flat table, though, and that's where most DIY migrations run into trouble.

Table of Contents
  1. What a "Product" Actually Is in WooCommerce
  2. Products with Variations
  3. Categories and Attributes
  4. Reviews
  5. Orders and Customer History
  6. What This Means for a Real Migration Plan

What a "Product" Actually Is in WooCommerce

A WooCommerce product is a WordPress post (post type product) plus a sprawl of related data: price and stock in postmeta, category and tag terms in the taxonomy tables, image galleries as attachment relationships, and — if it's a variable product — a full set of child "variation" posts each with their own price and stock overrides. Migrating "the product" means migrating all of that consistently, not just the title and description.

Products with Variations

Simple products (one price, one SKU) migrate cleanly. Variable products — a T-shirt in 4 sizes and 3 colors, each with its own price and stock level — are where migrations typically lose data, because each variation is technically its own hidden post linked to the parent. Map parent-to-variation relationships explicitly before you migrate; don't assume a flat product export captures them.

Read also:

Categories and Attributes

  • Product categories and tags are regular WordPress taxonomy terms — they migrate the same way post categories do.
  • Custom attributes (size, color, material) can be either "global" attributes (shared taxonomy, reusable across products) or product-local — check which each one is, since global attributes need their own taxonomy migrated separately from the products that use them.

Reviews

WooCommerce product reviews are WordPress comments under the hood, filtered to show star ratings. If your migration script only moves post content, reviews get silently dropped — they live in wp_comments, not anywhere near the product's own row. Star rating data itself is stored as comment meta, so it needs its own explicit mapping too.

Orders and Customer History

Whether order history needs to migrate at all depends on whether the new platform is taking over as the live store or just the content/catalog side. If you're moving the catalog to a headless CMS while keeping WooCommerce (or another platform) as the actual checkout, orders can often stay where they are — only products, categories, and reviews need to move.

What This Means for a Real Migration Plan

  • Export products and their variations together, with the parent-child relationship intact.
  • Migrate category/attribute taxonomies before the products that reference them, not after.
  • Pull reviews from wp_comments explicitly — don't assume they're bundled with product export.
  • Decide up front whether checkout/orders are in scope, since that changes the whole shape of the migration.

This is a narrower, higher-stakes version of the same problem covered in our full WordPress export guide and migration checklist — the general steps still apply, but WooCommerce's relational data model means you can't treat product migration as "just another post type." If you're also swapping out WooCommerce extensions in the process, see our breakdown of what replaces which WordPress plugin after a migration.

Share

Comments

Write a comment

Related Articles

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

July 16, 2026

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

July 16, 2026

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

July 16, 2026

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

TL;DR — A custom session-cookie login flow appeared to succeed on localhost (the OTP verified, the response looked fine) but every subsequent request to a login-gated page treated the visitor as logged out. Identical code worked fine on the live HTTPS site. The cookie's Secure attribute was hardcoded to true — and per the cookie spec, browsers never store or send a Secure cookie over a plain, non-HTTPS connection.

Table of Contents
  1. Diagnostic
  2. Root cause
  3. Fix
  4. Lessons learned

Diagnostic

Check the actual Set-Cookie response header and the browser's own cookie storage panel — on localhost over http://, the cookie is sent by the server but never actually stored by the browser.

Root cause

// before -- assumes the app is always served over HTTPS
setCookie("session", token, { secure: true, httpOnly: true });
emdashkits.com

A cookie config that quietly assumes "we're always on HTTPS" breaks the instant you test over plain HTTP, which local dev servers commonly are.

Read also:

Fix

// after -- derive secure from the actual request protocol
const isHttps = request.url.startsWith("https://");
setCookie("session", token, { secure: isHttps, httpOnly: true });
emdashkits.com

Lessons learned

  • Any Secure-flagged cookie needs to key off the real request scheme, not an assumption baked in once at cookie-creation time.
  • "Works in production, silently fails in local dev" is a strong signal to check cookie flags before anything else in an auth flow.
  • Check other cookies in the same codebase for the same hardcoded assumption — if one cookie has this bug, sibling cookies set the same way are worth auditing too.
Share
Previous Article

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

Comments

Write a comment

Related Articles

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

July 16, 2026

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

July 16, 2026

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

Uploaded Images Disappear After Every Deploy on Shared Hosting (and Other Reverse-Proxy Gotchas)

July 16, 2026

Uploaded Images Disappear After Every Deploy on Shared Hosting (and Other Reverse-Proxy Gotchas)

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

TL;DR — GA4's gtag.js snippet was installed correctly, the page loaded with no visible JavaScript error, and GA4's real-time report still showed zero activity. The CMS's default Content-Security-Policy had no allowance for analytics domains and no config option to add one — so every request to Google's tracking endpoints was blocked at the browser level before it could fail loudly.

Table of Contents
  1. Diagnostic
  2. Root cause
  3. Lessons learned

Diagnostic

Check the browser's dedicated CSP violation reporting, not the regular console error list — CSP blocks are reported through their own channel, not thrown as normal script errors, so "no console errors" doesn't mean nothing was blocked.

Root cause

The CSP's script-src and connect-src directives had no entry for googletagmanager.com or google-analytics.com, and the CMS exposed no configuration surface to add one — the only way in was patching the CSP directives directly.

// patch-package: add analytics domains to the existing CSP directives
scriptSrc.push("https://www.googletagmanager.com");
connectSrc.push("https://www.google-analytics.com", "https://www.googletagmanager.com");
emdashkits.com
Read also:

Lessons learned

  • "No console errors" is not proof nothing was blocked — CSP violations live in their own reporting surface and are easy to miss if you're only scanning for red error text.
  • Before adding any third-party script tag to a site with a CSP already in place, check the CSP's directives first rather than assuming a silently-empty analytics dashboard means a snippet-installation mistake.
Share
Previous Article

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

Next Article

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

Comments

Write a comment

Related Articles

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

July 16, 2026

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

July 16, 2026

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

Uploaded Images Disappear After Every Deploy on Shared Hosting (and Other Reverse-Proxy Gotchas)

July 16, 2026

Uploaded Images Disappear After Every Deploy on Shared Hosting (and Other Reverse-Proxy Gotchas)