Export WooCommerce Orders to CSV Programmatically

A production-safe WooCommerce CSV export should query orders through WooCommerce APIs, process them in bounded batches, stream rows instead of building the whole file in memory, protect personal data, and record who exported what. Direct queries against wp_posts and wp_postmeta are not portable across High-Performance Order Storage.

Recommended architecture: validate an authorized request, create an export job, query order IDs with wc_get_orders(), generate a UTF-8 CSV in batches, store it outside the public web root, provide an expiring download, then delete it according to policy.

Define the export contract first

  • Which order statuses and date range are allowed
  • Whether dates use created, paid, completed, or modified time
  • Which fields are required and their exact column names
  • How refunds, taxes, fees, discounts, shipping, and currencies are represented
  • Whether one row represents an order or a line item
  • How personal data is minimized, masked, retained, and deleted
  • Who may run and download the export
  • How retries, duplicates, and partial failures are recorded

A stable schema matters more than a clever query. Version the export format when downstream finance, fulfillment, or analytics systems depend on it.

Use WooCommerce order APIs for HPOS compatibility

WooCommerce recommends wc_get_orders() and WC_Order_Query instead of storage-specific SQL. Query IDs in pages, then load each order through WooCommerce objects.

$page = 1;

do {
    $result = wc_get_orders(
        array(
            'status'   => array( 'wc-processing', 'wc-completed' ),
            'limit'    => 100,
            'page'     => $page,
            'paginate' => true,
            'return'   => 'ids',
            'orderby'  => 'ID',
            'order'    => 'ASC',
        )
    );

    foreach ( $result->orders as $order_id ) {
        $order = wc_get_order( $order_id );

        if ( ! $order ) {
            continue;
        }

        // Transform and write one or more CSV rows.
    }

    ++$page;
} while ( $page <= $result->max_num_pages );

For a long-running export, a moving dataset can change between pages. Record a cutoff time or maximum order ID at job creation, and make the ordering deterministic. Decide whether changed orders require a later delta export.

Stream CSV rows safely

Use fputcsv() with a real file handle. Write the header once and stream each row. Do not join values with commas manually because quotes, commas, and line breaks need escaping.

$handle = fopen( $private_path, 'wb' );

fputcsv(
    $handle,
    array( 'order_id', 'created_at', 'status', 'currency', 'total' )
);

fputcsv(
    $handle,
    array(
        $order->get_id(),
        $order->get_date_created()
            ? $order->get_date_created()->date( DATE_ATOM )
            : '',
        $order->get_status(),
        $order->get_currency(),
        $order->get_total(),
    )
);

fclose( $handle );

Choose and document UTF-8 encoding, line endings, delimiter, enclosure, decimal rules, timezone, and formula-injection handling. Spreadsheet applications may interpret cells beginning with characters such as equals, plus, minus, or at-sign as formulas. Neutralize untrusted text according to the receiving system’s documented requirements.

Order rows versus line-item rows

An order-level export suits finance summaries. A line-item export repeats order identifiers for each product, shipping line, fee, coupon, tax, or refund. Never hide this difference. Totals can be wrong when a consumer assumes one row per order.

Use WooCommerce getters for billing, shipping, totals, and dates. Iterate $order->get_items() for products and use the specific item collections for fees, shipping, coupons, and taxes. Treat custom metadata as untrusted and map only approved keys.

Authorization and request safety

  • Require a dedicated capability instead of checking only whether a user is logged in.
  • Verify a nonce for browser-initiated actions. A nonce does not replace authorization.
  • Validate dates, statuses, formats, and requested fields against allowlists.
  • Keep exports outside public uploads or block direct web access.
  • Use short-lived, single-purpose download tokens.
  • Log requester, filters, row count, file checksum, result, and deletion time.
  • Rate-limit jobs and cap date ranges to protect the store.

Run large exports as background jobs

Do not keep an administrator’s HTTP request open for millions of rows. Queue a job, process a bounded batch, persist progress, and schedule the next batch. Make the job idempotent so a retry cannot silently duplicate or omit rows.

  • Store a job ID, schema version, filters, cutoff, cursor, counts, and status.
  • Write to a temporary file and rename it only after completion.
  • Record failed order IDs without exposing customer data in logs.
  • Support cancellation and clean up partial files.
  • Notify only authorized recipients and require a fresh authenticated download.
  • Monitor duration, memory, queue delay, failure rate, and file retention.

Testing checklist

  • HPOS enabled and legacy storage where the extension supports both
  • Orders with guests, registered customers, refunds, coupons, fees, taxes, shipping, and multiple currencies
  • Commas, quotes, line breaks, Unicode, right-to-left text, and spreadsheet-like formulas
  • Missing addresses, deleted products, custom order types, and unusual metadata
  • An export spanning multiple batches while orders change
  • Unauthorized requests, expired tokens, cancellation, retry, and cleanup
  • Reconciliation of exported counts and totals against a known report

For sensitive or high-volume workflows, see secure WordPress data exports at scale or enterprise WooCommerce development.

Primary sources

Frequently asked questions

Yes. WooCommerce recommends its order-query APIs so extensions do not depend on a particular order storage implementation.

Avoid storage-specific SQL for normal extension code. WooCommerce APIs preserve compatibility and business logic more reliably.

Create a background job, query stable batches, stream rows to a private file, persist progress, and provide an expiring download after completion.

Minimize columns, enforce capabilities, validate filters, store privately, use expiring downloads, log access, and delete files according to policy.

It depends on the contract. Order-level and line-item exports serve different needs. Document the row grain clearly.

Treat customer-controlled text as untrusted and neutralize formula-like cells according to the destination application’s requirements. Test the actual spreadsheet tool.

Mehul Gohil
Mehul Gohil

Mehul Gohil is a Full Stack WordPress developer and an active member of the local WordPress community. For the last 13+ years, he has been developing custom WordPress plugins, custom WordPress themes, third-party API integrations, performance optimization, and custom WordPress websites tailored to the client's business needs and goals.

Articles: 171

Leave a Reply

Your email address will not be published. Required fields are marked *