The Shopify Plus Migration That Saved $400K/Year
The Platform Tax
A DTC skincare brand was running on a custom-built commerce platform. Built 6 years ago by an agency that no longer existed. Three full-time engineers just to keep it running. Annual cost: $400K in salaries alone — plus $60K in hosting, $25K in PCI compliance, and uncounted hours of lost productivity.
Their competitors on Shopify Plus were shipping features in days. They were shipping bug fixes in weeks.
The Decision Framework
Before recommending migration, we ran the numbers:
Current custom platform costs (annual):
Engineering (3 FTE): $400,000
Hosting (AWS): $60,000
PCI compliance: $25,000
Payment processing: $90,000 (Stripe direct)
Lost revenue (downtime, bugs): ~$80,000
Total: $655,000
Shopify Plus projected costs (annual):
Platform fee: $24,000 ($2K/month)
Transaction fees (0.15%): $45,000 (on $30M GMV)
Apps and integrations: $36,000
Engineering (1 FTE): $160,000
Hosting: included
PCI compliance: included
Total: $265,000
Annual savings: $390,000
Migration cost (one-time): $180,000
Payback period: 5.5 months
The math was clear. But math doesn't migrate 50K SKUs and 200K customer accounts.
Phase 1: Data Audit (Week 1-2)
Before touching any code, we mapped every piece of data:
Data inventory:
✓ 50,247 SKUs (with variants, metafields, images)
✓ 203,891 customer accounts (with order history)
✓ 1.2M historical orders (for analytics continuity)
✓ 847 discount codes (active)
✓ 12,400 product reviews (with images)
✓ 340 blog posts
✓ 89 redirect rules
✓ Custom subscription logic (2,400 active subscribers)
Integration inventory:
✓ Klaviyo (email/SMS)
✓ Yotpo (reviews)
✓ Recharge (subscriptions)
✓ ShipStation (fulfillment)
✓ NetSuite (ERP/accounting)
✓ Google Analytics + Meta Pixel
✓ Gorgias (customer support)
Phase 2: Migration Scripts (Week 3-6)
We wrote idempotent migration scripts that could be run repeatedly:
// Product migration — handle Shopify's API rate limits
async function migrateProducts(products: SourceProduct[]) {
const batcher = new ShopifyBatcher({
maxRequestsPerSecond: 4, // Shopify Plus rate limit
retryOnThrottle: true,
});
for (const product of products) {
await batcher.queue(async () => {
// Map custom fields to Shopify metafields
const shopifyProduct = {
title: product.name,
body_html: product.description,
vendor: product.brand,
product_type: product.category,
variants: product.variants.map(v => ({
sku: v.sku,
price: v.price.toString(),
compare_at_price: v.comparePrice?.toString(),
inventory_quantity: v.stock,
weight: v.weight,
weight_unit: "g",
})),
metafields: [
{ namespace: "custom", key: "ingredients", value: product.ingredients },
{ namespace: "custom", key: "usage_instructions", value: product.usage },
],
};
const created = await shopify.product.create(shopifyProduct);
// Upload images separately (parallel for speed)
await Promise.all(product.images.map((img, i) =>
shopify.productImage.create(created.id, {
src: img.url,
position: i + 1,
alt: img.alt || product.name,
})
));
// Store mapping for later reference
await db.mappings.insert({
source_id: product.id,
shopify_id: created.id,
type: "product",
});
});
}
}Customer Migration
Customer accounts were the most delicate migration:
Customer migration strategy:
1. Migrate customer data (name, email, address, order history)
2. DO NOT migrate passwords (can't — they're hashed)
3. Send "Welcome to our new platform" email with password reset link
4. For subscribers: migrate through Recharge's migration tool
5. Verify: order history accessible in customer accounts
Timeline:
→ Test migration with 100 customers: Week 4
→ Full migration: Week 7 (during parallel run)
→ Password reset campaign: Launch day
Phase 3: Theme Development (Week 3-8, parallel)
We rebuilt the storefront using Shopify's Dawn theme as a base:
Theme priorities:
1. Product page (60% of traffic) — match or beat current conversion rate
2. Collection pages — faster filtering, better mobile experience
3. Cart + checkout — Shopify checkout (higher conversion than custom)
4. Homepage — new design with better performance
5. Content pages — migrated from custom CMS to Shopify metaobjects
Performance targets:
LCP: < 1.5s (current: 3.8s)
FID: < 100ms (current: 340ms)
CLS: < 0.1 (current: 0.34)
Phase 4: Parallel Run (Week 9-10)
Both platforms ran simultaneously. The custom platform handled real orders. Shopify received mirrored traffic:
Parallel run validation:
✓ Product data matches (price, inventory, descriptions)
✓ Cart calculations match (discounts, tax, shipping)
✓ Checkout flow completes without errors
✓ Analytics events fire correctly
✓ Email triggers work (order confirmation, shipping, etc.)
✓ Subscription creation works through Recharge
✓ Customer accounts accessible with order history
Phase 5: Cutover (Week 11)
The cutover happened on a Tuesday night (lowest traffic period):
Cutover timeline:
8:00 PM: Final data sync (orders from last 24h)
8:30 PM: DNS switch (old domain → Shopify)
8:35 PM: Verify site loads on new platform
8:45 PM: Place test orders (standard, subscription, discount)
9:00 PM: Monitor error rates, conversion rate
9:30 PM: Enable customer password reset emails
10:00 PM: All clear — cutover complete
Rollback plan:
→ DNS revert to old platform (< 5 min)
→ Old platform kept hot for 30 days
→ Any orders placed on Shopify during rollback: manually process
The Results
After migration (30-day comparison):
Page speed (LCP): 3.8s → 1.4s (-63%)
Mobile conversion rate: 1.8% → 2.6% (+44%)
Desktop conversion rate: 3.2% → 3.8% (+19%)
Cart abandonment: 74% → 68% (-6 points)
Engineering headcount: 3 FTE → 1 FTE (2 redeployed to product features)
Monthly platform cost: $54K → $22K (-59%)
Revenue impact (annualized):
Conversion improvement: +$1.2M
Cost reduction: +$390K
Total annual benefit: +$1.59M
The best technology decision isn't always the most technically interesting one. Sometimes it's moving to a platform that lets your team focus on what actually differentiates your brand — not on keeping the lights on.