By Devport Team | Last updated: 2026-10-04 | 9 min read

Migrating WooCommerce Data to a Custom Application with the REST API

Moving a store from WooCommerce to a custom application (or another platform) is mostly a data problem: products, variations, customers and order history have to be exported completely and mapped to a new model. The WooCommerce REST API is usually the safest way to get that data out, because it returns the same structure regardless of how the store stores orders internally. This guide outlines a practical export plan.

Table of Contents

  1. Plan the migration
  2. Set up read-only access
  3. Export order: what to pull first
  4. An export script
  5. Data that needs special handling
  6. Incremental sync and cut-over

Plan the Migration

Set Up Read-Only Access

Create a dedicated API key with Read permission for the export (how to create keys). The key's user needs permission to view orders and customers, so use a Shop Manager or Administrator account. Run exports against a recent copy of the store if the live server is under load.

Export Order: What to Pull First

  1. Reference data: /products/categories, /products/tags, /products/attributes (+ terms), /taxes, /shipping/zones.
  2. /products, then /products/<id>/variations for every variable product.
  3. /customers?role=all (customers who checked out as guests only exist inside orders).
  4. /orders, plus /orders/<id>/refunds and /orders/<id>/notes where you need them.
  5. /coupons.

See the wc/v3 endpoints reference for parameters.

An Export Script

This Node.js script pages through a collection and writes newline-delimited JSON, which is easy to re-import and diff:

// export.mjs  -  node export.mjs products orders customers
import { createWriteStream } from 'node:fs';

const BASE = process.env.WC_URL; // e.g. https://shop.example.com/wp-json/wc/v3
const AUTH = 'Basic ' + Buffer.from(`${process.env.WC_KEY}:${process.env.WC_SECRET}`).toString('base64');

async function get(path) {
  for (let attempt = 1; ; attempt++) {
    const res = await fetch(`${BASE}/${path}`, { headers: { Authorization: AUTH } });
    if (res.ok) return res;
    if (attempt >= 5 || res.status < 500) throw new Error(`${res.status} on ${path}`);
    await new Promise((r) => setTimeout(r, 1000 * attempt)); // back off on 5xx
  }
}

async function exportCollection(name, query = '') {
  const out = createWriteStream(`${name}.ndjson`);
  let page = 1, totalPages = 1;
  do {
    const res = await get(`${name}?per_page=100&page=${page}&orderby=id&order=asc${query}`);
    totalPages = Number(res.headers.get('X-WP-TotalPages') || 1);
    for (const item of await res.json()) out.write(JSON.stringify(item) + '
');
    console.log(`${name}: page ${page}/${totalPages}`);
    page++;
  } while (page <= totalPages);
  out.end();
}

for (const name of process.argv.slice(2)) {
  await exportCollection(name, name === 'customers' ? '&role=all' : '');
}

Sorting by id keeps pages stable while new orders arrive during the export. For variations, read products.ndjson, filter type === "variable" and call products/<id>/variations for each.

Data That Needs Special Handling

Incremental Sync and Cut-Over

  1. Run a full export and import into the new system; reconcile counts (X-WP-Total) and order totals.
  2. Keep the systems in step with incremental pulls using modified_after (supported on recent WooCommerce versions) or with webhooks for order.*, customer.* and product.*.
  3. At cut-over, put the store in maintenance mode, run a final incremental sync, switch DNS, and keep the old site read-only for reference.
  4. Revoke the export API key once the migration is signed off.

Related: WooCommerce REST API guide, adding a custom endpoint (useful for exporting protected meta), and headless WordPress if you only want to replace the front end.