Migration Guide
Step-by-step guide to migrating WooCommerce data to FluentCart. Back up, preflight, migrate, verify. Every entity type explained in detail.
This guide walks through the complete migration process from start to finish. Follow each step in order — the dependency chain between entity types matters.
Back up your database first
This is non-negotiable. Before running any migration — even a dry run — create a full database backup. If something goes wrong, your backup is the only guaranteed way to restore your WooCommerce data exactly as it was. The migrator's rollback feature deletes FluentCart records, but it does not restore WooCommerce data.
Migration Steps
Back up your database
Use your preferred backup method. Some options:
- Hosting panel — Most hosts offer one-click database backups via cPanel, Plesk, or a custom panel
- WP CLI —
wp db export backup.sql - Plugin — UpdraftPlus, BlogVault, or similar backup plugins
- phpMyAdmin — Export your full database as SQL
Store the backup somewhere safe and accessible. You may need it if the migration produces unexpected results and you want to start completely fresh.
If your store has a large database (over 1 GB), make sure the backup completes fully before proceeding. Partial backups will not help you.
Run preflight checks
Navigate to Tools > CartShift or call the preflight endpoint directly:
curl -X GET "https://yoursite.com/wp-json/cartshift/v1/preflight" \
-H "X-WP-Nonce: YOUR_NONCE"All required checks must pass before you proceed. See Preflight Checks for detailed information about each check and how to fix failures.
Review entity counts
Check how many records will be migrated:
curl -X GET "https://yoursite.com/wp-json/cartshift/v1/counts" \
-H "X-WP-Nonce: YOUR_NONCE"{
"products": 142,
"categories": 12,
"customers": 856,
"orders": 3241,
"subscriptions": 94,
"coupons": 27
}Review these numbers against what you expect. If the counts look wrong, investigate your WooCommerce data before migrating. Common causes of unexpected counts:
- Products — Only
simpleandvariabletypes are counted. Grouped and external products are excluded. - Customers — Includes both registered users with the
customerrole and unique guest emails from orders. - Subscriptions — Returns 0 if WC Subscriptions is not active.
Run a dry run (recommended)
Before writing any data, run the migration in dry-run mode. Every record is validated, mapped, and logged — but nothing is written to FluentCart.
curl -X POST "https://yoursite.com/wp-json/cartshift/v1/migrate" \
-H "X-WP-Nonce: YOUR_NONCE" \
-H "Content-Type: application/json" \
-d '{
"entity_types": ["products", "customers", "coupons", "orders", "subscriptions"],
"dry_run": true
}'After the dry run completes, check the migration log:
curl -X GET "https://yoursite.com/wp-json/cartshift/v1/log?per_page=100" \
-H "X-WP-Nonce: YOUR_NONCE"Look for entries with status: "error". Fix any issues before proceeding with the real migration. Common dry-run findings:
- Unsupported product types (grouped, external) are logged as skipped
- Products with missing prices are flagged
- Guest customers with empty emails are skipped
Start the migration
When you are ready, start the real migration:
curl -X POST "https://yoursite.com/wp-json/cartshift/v1/migrate" \
-H "X-WP-Nonce: YOUR_NONCE" \
-H "Content-Type: application/json" \
-d '{
"entity_types": ["products", "customers", "coupons", "orders", "subscriptions"]
}'The entity_types array controls which data gets migrated. You can migrate selectively:
{ "entity_types": ["products", "customers"] }Migration order
Regardless of the order you specify in entity_types, the migrator always processes entities in dependency order: products > customers > coupons > orders > subscriptions. This ensures that orders can reference the correct product and customer IDs.
The migration runs synchronously — the REST request will not return until all entity types have been processed or the migration is cancelled. For large stores, this can take several minutes.
Monitor progress
While the migration runs, poll the progress endpoint from a separate request:
curl -X GET "https://yoursite.com/wp-json/cartshift/v1/progress" \
-H "X-WP-Nonce: YOUR_NONCE"{
"migration_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "running",
"dry_run": false,
"started_at": "2025-03-15 10:30:00",
"completed_at": null,
"entities": {
"products": {
"status": "completed",
"total": 142,
"processed": 138,
"skipped": 4,
"errors": 0
},
"customers": {
"status": "running",
"total": 856,
"processed": 312,
"skipped": 5,
"errors": 0
},
"coupons": {
"status": "pending",
"total": 0,
"processed": 0,
"skipped": 0,
"errors": 0
},
"orders": {
"status": "pending",
"total": 0,
"processed": 0,
"skipped": 0,
"errors": 0
},
"subscriptions": {
"status": "pending",
"total": 0,
"processed": 0,
"skipped": 0,
"errors": 0
}
}
}Each entity reports:
- status —
pending,running, orcompleted - total — Total records to process
- processed — Successfully migrated records
- skipped — Records skipped (already migrated, duplicates, unsupported types)
- errors — Records that failed to migrate
When the migration finishes, the overall status changes to completed and completed_at gets a timestamp.
Cancel if needed
If something goes wrong during migration, you can cancel it:
curl -X POST "https://yoursite.com/wp-json/cartshift/v1/cancel" \
-H "X-WP-Nonce: YOUR_NONCE"The migrator checks for cancellation between every record, so it stops cleanly. Records already migrated before cancellation remain in FluentCart — use rollback if you want to remove them.
Verify migrated data
After migration completes, verify your data in FluentCart:
- Products — Check that products appear in FluentCart with correct titles, prices, descriptions, images, and variations
- Categories — Verify the category hierarchy is intact
- Customers — Check customer records with addresses in FluentCart
- Orders — Verify order history, line items, totals, and statuses
- Subscriptions — Check subscription statuses, billing intervals, and linked products
- Coupons — Verify coupon codes, types, and amounts
Also review the migration log for any errors:
curl -X GET "https://yoursite.com/wp-json/cartshift/v1/log?per_page=100" \
-H "X-WP-Nonce: YOUR_NONCE"Deactivate the plugin
Once you have verified everything, deactivate CartShift. It has served its purpose.
Go to Plugins and click Deactivate on "CartShift". The cartshift_migration_state option is cleaned up on deactivation.
Keep the plugin installed (but deactivated) for a while in case you need to reference the migration log or ID mapping table. Only delete it when you are completely confident in the migrated data.
Entity Migration Details
Each entity type has specific mapping behavior. Understanding these details helps you verify your migrated data and troubleshoot any issues.
Products
ProductMigrator + ProductMapper + VariationMapperSupported types: Simple and variable products. Grouped and external products are skipped.
What gets mapped:
| WooCommerce | FluentCart |
|---|---|
| Product name | post_title (CPT: fluent-products) |
| Description | post_content |
| Short description | post_excerpt |
| Product status (publish/draft/private) | post_status (via StatusMapper) |
| Slug | post_name |
| Featured image | Copied via set_post_thumbnail() |
| Product categories | Mapped to product-categories taxonomy |
Product details (fct_product_details table):
- Fulfillment type:
physical,digital, orservicebased on WC product properties - Variation type:
simpleoradvanced_variations - Stock availability and management settings
- Min/max price range calculated from variations
Variations (fct_product_variations table):
- Simple products get one "Default" variation
- Variable products get one variation per WC variation
- Prices converted to cents (integer) —
$29.99becomes2999 - Sale price becomes
compare_price(original regular price) - SKU uniqueness enforced — duplicates get a
-wc{id}suffix - Stock quantities, backorder settings, and downloadable flags preserved
- WC Subscription metadata (billing period, trial, signup fee) mapped to
other_info
Category migration runs before products. Categories are sorted so parents are created before children. The "Uncategorized" default category is skipped. Categories already existing in FluentCart (matched by slug) are reused, not duplicated.
Customers
CustomerMigrator + CustomerMapperRegistered customers:
WordPress users with the customer role. Each user becomes a FluentCart customer record with billing/shipping addresses.
| WooCommerce | FluentCart |
|---|---|
user_email | email |
billing_first_name (or first_name) | first_name |
billing_last_name (or last_name) | last_name |
billing_country, billing_city, etc. | country, city, state, postcode |
| User ID | user_id (links FC customer to WP user) |
Guest customers:
Extracted from orders that have no customer_id (or customer_id = 0). Each unique billing email becomes a FluentCart customer. The migrator supports both HPOS (wc_orders table) and legacy post-based order storage.
Addresses:
Both billing and shipping addresses are created as CustomerAddresses records in FluentCart. Phone numbers and company names are stored in the address meta field.
Deduplication: If a customer with the same email already exists in FluentCart, the migrator stores the ID mapping and skips creation.
Orders
OrderMigrator + OrderMapper + StatusMapperOrders are the most complex entity to migrate. Each WooCommerce order maps to multiple FluentCart tables.
Order record (fct_orders):
| WooCommerce | FluentCart |
|---|---|
| Order status | status and payment_status (via StatusMapper) |
| Payment method | payment_method and payment_method_title |
| Currency | currency |
| Subtotal, tax, shipping, total | Converted to cents |
| Customer note | note |
| IP address | ip_address |
| Customer ID | Resolved via ID map |
Status mapping:
| WC Status | FC Order Status | FC Payment Status |
|---|---|---|
| pending | pending | pending |
| processing | processing | paid |
| on-hold | on-hold | pending |
| completed | completed | paid |
| cancelled | failed | failed |
| refunded | refunded | refunded |
| failed | failed | failed |
Order items (fct_order_items):
Each line item references the FluentCart product and variation via the ID map. Unit prices and line totals are converted to cents. Subscription product metadata is preserved.
Order addresses (fct_order_addresses):
Billing and shipping addresses stored per-order. Company names and phone numbers in the meta field.
Transactions (fct_order_transactions):
The primary payment creates a charge transaction. Refunds create separate refund transactions linked to the same order. WooCommerce transaction IDs are preserved in the meta field.
Order types:
- Regular orders become
onetime - Orders containing subscription products become
subscription - Renewal orders (child orders from WC Subscriptions) become
renewal
HPOS support: The migrator works with both HPOS (High-Performance Order Storage) and legacy post-based orders. Customer counting and guest extraction auto-detect which storage is active.
Subscriptions
SubscriptionMigrator + SubscriptionMapperRequires WC Subscriptions to be active. If WC Subscriptions is not detected, the subscription migrator returns null and is skipped entirely.
What gets mapped:
| WooCommerce | FluentCart |
|---|---|
| Billing period (day/week/month/year) | billing_interval (daily/weekly/monthly/yearly) |
| Recurring total | recurring_total (cents) |
| Recurring tax | recurring_tax_total (cents) |
| Signup fee (calculated from parent order) | signup_fee (cents) |
| Payment count | bill_count |
| Bill times (length / interval) | bill_times |
| Trial end date | trial_ends_at and trial_days |
| Next payment date | next_billing_date |
| End date | expire_at |
| Cancelled date | canceled_at |
| Start date | created_at |
| Customer ID | Resolved via ID map |
| Parent order ID | Resolved via ID map |
| Product and variation IDs | Resolved via ID map |
Subscription status mapping:
| WC Status | FC Status |
|---|---|
| active | active |
| on-hold | paused |
| cancelled | canceled |
| expired | expired |
| pending-cancel | expiring |
| pending | pending |
| switched | canceled |
Per-product subscriptions
FluentCart subscriptions are per-product. If a WC Subscription contains multiple line items, the migrator uses the first item's product and variation. This matches FluentCart's subscription model.
Coupons
CouponMigrator + CouponMapperWhat gets mapped:
| WooCommerce | FluentCart |
|---|---|
| Coupon code | code (uppercased) |
| Discount type | type (percentage or fixed) |
| Amount | amount (cents for fixed, float for percentage) |
| Individual use | stackable (inverted: individual_use=true becomes stackable=no) |
| Usage count | use_count |
| Description | notes |
| Date created | start_date |
| Expiry date | end_date |
Coupon type mapping:
| WC Type | FC Type |
|---|---|
| percent | percentage |
| fixed_cart | fixed |
| fixed_product | fixed |
Conditions stored in the conditions JSON field:
- Minimum order amount
- Maximum order amount
- Usage limit (max uses)
- Per-customer usage limit
- Exclude sale items flag
- Free shipping flag
Deduplication: If a coupon with the same code already exists in FluentCart, it is skipped and the mapping is stored.
Batch Processing
All entity types are processed in batches of 50 records. This is controlled by the $batchSize property in AbstractMigrator.
The batch loop works as follows:
- Count total records for the entity type
- Fetch a batch of records (page 1, 2, 3...)
- Process each record in the batch (map, validate, write)
- Update progress after each record
- Check for cancellation between records
- Move to the next batch until all records are processed or the batch returns fewer than 50 records
This approach keeps memory usage predictable regardless of store size. A store with 10,000 orders processes them in 200 batches of 50.
ID Mapping
The cartshift_id_map table is central to the migration. Every time a WooCommerce record is successfully migrated, its WC ID and FC ID are stored along with the entity type.
Entity types in the ID map:
| Entity Type | Description |
|---|---|
category | Product category term IDs |
product | Product post IDs |
product_detail | Product detail row IDs |
variation | Product variation IDs |
customer | Registered customer IDs |
guest_customer | Guest customer IDs (using CRC32 of email) |
customer_address | Customer address IDs |
order | Order IDs |
order_item | Order item IDs |
order_address | Order address IDs |
order_transaction | Order transaction IDs |
coupon | Coupon IDs |
subscription | Subscription IDs |
The ID map is used for:
- Cross-referencing — Orders look up customer and product IDs from the map
- Skip detection — Already-migrated records are skipped by checking the map
- Rollback — The rollback process uses the map to find and delete FluentCart records
Migration Log
Every processed record generates a log entry in the cartshift_migration_log table:
| Column | Description |
|---|---|
migration_id | UUID of the migration run |
entity_type | Entity being migrated |
wc_id | WooCommerce record ID |
status | success, skipped, or error |
message | Human-readable description of what happened |
created_at | Timestamp |
Retrieve the log via the REST endpoint:
curl -X GET "https://yoursite.com/wp-json/cartshift/v1/log?page=1&per_page=50" \
-H "X-WP-Nonce: YOUR_NONCE"The response includes pagination information:
{
"entries": [ ... ],
"total": 4360,
"page": 1,
"per_page": 50,
"pages": 88
}Preflight Checks
Validate your environment before migration. The preflight system checks WooCommerce, FluentCart, PHP memory, WC Subscriptions availability, and existing FluentCart data.
Rollback
Undo a migration by removing all FluentCart records created by the migrator. Rollback uses the ID map to delete records in reverse dependency order.