Dev Pattern Labs logo
Dev Pattern Labs Blog
WooCommerce 11 min readOct 13, 2026

WooCommerce REST API: Managing Orders and Inventory

Learn to manage WooCommerce orders and inventory programmatically via the REST API. Covers listing orders, updating status, adding notes, processing refunds, and batch stock management.

Dev Pattern Labs Author

Dev Pattern Labs

Peer-reviewed technical tutorial · Production tested

E-commerce dashboard showing orders and inventory management
Executive Summary & Key Insight

Learn to manage WooCommerce orders and inventory programmatically via the REST API. Covers listing orders, updating status, adding notes, processing refunds, and batch stock management. This practical guide covers step-by-step setup, real-world code snippets, security hardening, and common production edge cases.

Advertisement

WooCommerce powers over 30% of all online stores worldwide, and much of that success comes from its extensibility. At the heart of that extensibility is the WooCommerce REST API — a fully OAuth-authenticated, JSON-based interface that gives developers programmatic control over virtually every aspect of a store. Whether you are building a custom fulfilment dashboard, syncing inventory with a warehouse management system, or automating post-purchase workflows, the REST API is the tool you need.

Managing orders and inventory through the API unlocks a level of automation that is simply impossible through the WordPress admin UI alone. Imagine automatically marking orders as "completed" the moment a shipment label is generated, or triggering a low-stock alert email only when your warehouse confirms a pick. These are not edge-case scenarios — they are day-to-day operational needs for growing e-commerce businesses. The REST API makes them straightforward to implement in any language that can make HTTP requests.

In this guide we will walk through everything you need: authenticating with the API, retrieving and filtering orders, updating statuses, leaving order notes, issuing refunds, and performing bulk stock operations — all with real, copy-paste-ready TypeScript examples. By the end, you will have a solid blueprint for integrating WooCommerce order and inventory data into any backend system or automation pipeline.


Prerequisites

Before diving in, make sure you have the following in place:

  • A running WooCommerce store (version 3.5+) with the REST API enabled under WooCommerce → Settings → Advanced → REST API.
  • A generated Consumer Key and Consumer Secret (read/write scope).
  • Node.js 18+ installed locally (for the TypeScript examples).
  • The axios npm package installed: npm install axios.
  • Basic familiarity with REST APIs and TypeScript.

1. Setting Up WooCommerce REST API Authentication

WooCommerce supports two authentication mechanisms: OAuth 1.0a for HTTP connections and Basic Auth over HTTPS for secure connections. For any production integration, always use HTTPS with Basic Auth — it is simpler to implement and equally secure when TLS is properly configured.

Start by creating a small reusable Axios client that encodes your credentials and sets sensible defaults:

// lib/wooClient.ts
import axios from "axios";

const WOO_BASE_URL = process.env.WOO_BASE_URL!;       // e.g. https://mystore.com
const CONSUMER_KEY = process.env.WOO_CONSUMER_KEY!;
const CONSUMER_SECRET = process.env.WOO_CONSUMER_SECRET!;

export const wooClient = axios.create({
  baseURL: `${WOO_BASE_URL}/wp-json/wc/v3`,
  auth: {
    username: CONSUMER_KEY,
    password: CONSUMER_SECRET,
  },
  headers: {
    "Content-Type": "application/json",
  },
});

// Optional: Global response interceptor for error handling
wooClient.interceptors.response.use(
  (response) => response,
  (error) => {
    const message =
      error.response?.data?.message ?? error.message ?? "Unknown API error";
    console.error(`[WooCommerce API Error] ${error.response?.status}: ${message}`);
    return Promise.reject(error);
  }
);

Store your credentials in a .env file and never commit them to version control. The wooClient instance is now ready to import throughout your integration code.


2. Listing and Filtering Orders by Status

The /orders endpoint supports a rich set of query parameters. You can filter by status, date range, customer, product, and more. Here is how to retrieve all processing orders placed in the last 30 days, paginating through results automatically:

// orders/listOrders.ts
import { wooClient } from "../lib/wooClient";

interface WooOrder {
  id: number;
  status: string;
  date_created: string;
  total: string;
  billing: { first_name: string; last_name: string; email: string };
  line_items: Array<{ product_id: number; name: string; quantity: number }>;
}

async function fetchOrdersByStatus(
  status: string,
  afterDate: string
): Promise<WooOrder[]> {
  let page = 1;
  const perPage = 100;
  const allOrders: WooOrder[] = [];

  while (true) {
    const { data, headers } = await wooClient.get<WooOrder[]>("/orders", {
      params: {
        status,
        after: afterDate,      // ISO 8601 date string
        page,
        per_page: perPage,
        orderby: "date",
        order: "desc",
      },
    });

    allOrders.push(...data);

    const totalPages = parseInt(headers["x-wp-totalpages"] ?? "1", 10);
    if (page >= totalPages) break;
    page++;
  }

  return allOrders;
}

// Usage
const thirtyDaysAgo = new Date(
  Date.now() - 30 * 24 * 60 * 60 * 1000
).toISOString();

const processingOrders = await fetchOrdersByStatus("processing", thirtyDaysAgo);
console.log(`Found ${processingOrders.length} processing orders.`);

The x-wp-totalpages response header tells you how many pages exist for the current query, making automatic pagination reliable without over-fetching.


3. Updating Order Status Programmatically

Changing an order's status is a simple PUT request to /orders/{id}. This is the most common write operation you will perform — typically from a fulfilment webhook or a cron job that polls a shipping carrier API:

// orders/updateOrderStatus.ts
import { wooClient } from "../lib/wooClient";

type WooOrderStatus =
  | "pending"
  | "processing"
  | "on-hold"
  | "completed"
  | "cancelled"
  | "refunded"
  | "failed"
  | "trash";

async function updateOrderStatus(
  orderId: number,
  newStatus: WooOrderStatus
): Promise<void> {
  const { data } = await wooClient.put(`/orders/${orderId}`, {
    status: newStatus,
  });

  console.log(
    `Order #${data.id} updated to "${data.status}" at ${data.date_modified}`
  );
}

// Mark order 1042 as completed once a shipment is confirmed
await updateOrderStatus(1042, "completed");

For bulk status transitions (e.g., marking hundreds of orders as completed after a batch shipment export), use the batch endpoint covered in Section 7 to avoid rate-limiting.


4. Adding Order Notes via the API

Order notes appear in the WooCommerce admin timeline and can optionally be emailed to the customer. They are invaluable for logging automated actions — tracking numbers, fraud check results, or custom workflow milestones:

// orders/addOrderNote.ts
import { wooClient } from "../lib/wooClient";

async function addOrderNote(
  orderId: number,
  note: string,
  customerNote: boolean = false
): Promise<void> {
  const { data } = await wooClient.post(`/orders/${orderId}/notes`, {
    note,
    customer_note: customerNote,  // true = visible to customer & triggers email
  });

  console.log(`Note #${data.id} added to Order #${orderId}: "${data.note}"`);
}

// Internal fulfilment note (not sent to customer)
await addOrderNote(
  1042,
  "Shipment exported to carrier. Tracking: 1Z999AA10123456784",
  false
);

// Customer-facing note (triggers WooCommerce email notification)
await addOrderNote(
  1042,
  "Your order has been dispatched! Tracking number: 1Z999AA10123456784",
  true
);

5. Processing Refunds via REST API

Refunds in WooCommerce are their own sub-resource living under /orders/{id}/refunds. A refund can be full or partial, and you can specify per-line-item quantities to ensure accurate restocking:

// orders/createRefund.ts
import { wooClient } from "../lib/wooClient";

interface RefundLineItem {
  id: number;           // line_item id from the original order
  quantity: number;
  refund_total: string; // must match price × quantity
  restock_items?: boolean;
}

async function createPartialRefund(
  orderId: number,
  lineItems: RefundLineItem[],
  reason: string
): Promise<void> {
  const refundAmount = lineItems
    .reduce((sum, item) => sum + parseFloat(item.refund_total), 0)
    .toFixed(2);

  const { data } = await wooClient.post(`/orders/${orderId}/refunds`, {
    amount: refundAmount,
    reason,
    line_items: lineItems,
    restock_items: true,   // automatically re-add stock on refund
  });

  console.log(
    `Refund #${data.id} of $${data.amount} issued for Order #${orderId}`
  );
}

// Refund one unit of line item 55, priced at $29.99
await createPartialRefund(
  1042,
  [{ id: 55, quantity: 1, refund_total: "29.99", restock_items: true }],
  "Item arrived damaged"
);

Setting restock_items: true instructs WooCommerce to automatically increment the product's stock quantity back to its pre-order level, keeping your inventory accurate without a separate API call.


6. Stock Management — manage_stock and stock_quantity

Before you can manipulate stock quantities, the product must have inventory management enabled. The manage_stock flag controls this at the product level (and optionally at the variation level for variable products):

// products/enableStockManagement.ts
import { wooClient } from "../lib/wooClient";

async function enableAndSetStock(
  productId: number,
  quantity: number,
  lowStockThreshold: number = 5
): Promise<void> {
  const { data } = await wooClient.put(`/products/${productId}`, {
    manage_stock: true,
    stock_quantity: quantity,
    low_stock_amount: lowStockThreshold,
    stock_status: quantity > 0 ? "instock" : "outofstock",
    backorders: "no",   // 'no' | 'notify' | 'yes'
  });

  console.log(
    `Product "${data.name}" — stock set to ${data.stock_quantity}, ` +
    `low-stock alert at ${data.low_stock_amount}`
  );
}

await enableAndSetStock(317, 150, 10);

For product variations, the endpoint changes to /products/{product_id}/variations/{variation_id} but the payload structure is identical.


7. Batch Stock Updates

When you need to update stock for dozens or hundreds of products simultaneously — for example after importing a supplier CSV — use the /products/batch endpoint. It accepts arrays of create, update, and delete objects in a single HTTP call:

// products/batchStockUpdate.ts
import { wooClient } from "../lib/wooClient";

interface StockUpdate {
  id: number;
  stock_quantity: number;
}

async function batchUpdateStock(updates: StockUpdate[]): Promise<void> {
  // WooCommerce batch endpoint handles up to 100 items per request
  const chunkSize = 100;

  for (let i = 0; i < updates.length; i += chunkSize) {
    const chunk = updates.slice(i, i + chunkSize);

    const { data } = await wooClient.post("/products/batch", {
      update: chunk.map(({ id, stock_quantity }) => ({
        id,
        stock_quantity,
        manage_stock: true,
      })),
    });

    console.log(
      `Batch chunk ${Math.floor(i / chunkSize) + 1}: ` +
      `${data.update.length} products updated`
    );
  }
}

// Update stock for multiple SKUs after warehouse sync
const stockUpdates: StockUpdate[] = [
  { id: 101, stock_quantity: 200 },
  { id: 102, stock_quantity: 45 },
  { id: 103, stock_quantity: 0 },
  { id: 317, stock_quantity: 78 },
];

await batchUpdateStock(stockUpdates);

Chunking at 100 items keeps individual request payloads small and within WooCommerce's recommended limits, and the loop ensures even large catalogues are fully processed.


8. Stock Reports and Low-Stock Queries

WooCommerce does not expose a dedicated low-stock report via the REST API, but you can build one by filtering the /products endpoint using stock_status and low_stock_amount. Here is a utility that pulls every product currently in a low-stock or out-of-stock state:

// reports/lowStockReport.ts
import { wooClient } from "../lib/wooClient";

interface ProductStockInfo {
  id: number;
  name: string;
  sku: string;
  stock_quantity: number | null;
  stock_status: string;
  low_stock_amount: number | null;
}

async function getLowStockProducts(): Promise<ProductStockInfo[]> {
  const results: ProductStockInfo[] = [];
  let page = 1;

  while (true) {
    const { data, headers } = await wooClient.get<ProductStockInfo[]>(
      "/products",
      {
        params: {
          manage_stock: true,
          stock_status: "instock",
          page,
          per_page: 100,
          _fields: "id,name,sku,stock_quantity,stock_status,low_stock_amount",
        },
      }
    );

    // Filter client-side: products where quantity <= low_stock_amount threshold
    const lowStock = data.filter(
      (p) =>
        p.stock_quantity !== null &&
        p.low_stock_amount !== null &&
        p.stock_quantity <= p.low_stock_amount
    );

    results.push(...lowStock);

    const totalPages = parseInt(headers["x-wp-totalpages"] ?? "1", 10);
    if (page >= totalPages) break;
    page++;
  }

  return results;
}

const lowStockItems = await getLowStockProducts();

console.table(
  lowStockItems.map((p) => ({
    ID: p.id,
    Name: p.name,
    SKU: p.sku,
    "In Stock": p.stock_quantity,
    "Alert At": p.low_stock_amount,
  }))
);

Using _fields to limit the returned properties significantly reduces payload size when scanning a large catalogue. You can pipe the resulting array into a Slack webhook, an email notification, or a Google Sheet export depending on your operational workflow.


WooCommerce Order Statuses Reference

Understanding the full order lifecycle is essential before automating status transitions. The table below lists every built-in WooCommerce order status, its slug (used in API calls), and its typical trigger:

StatusAPI SlugDescription
Pending paymentpendingOrder received, awaiting payment confirmation
ProcessingprocessingPayment confirmed, order being prepared
On holdon-holdAwaiting manual review or payment verification
CompletedcompletedOrder fulfilled and delivered
CancelledcancelledCancelled by admin or customer before fulfilment
RefundedrefundedFull refund issued
FailedfailedPayment failed or was declined
TrashtrashOrder soft-deleted (recoverable from WP trash)

Custom statuses (e.g. wc-awaiting-shipment) registered by plugins use the same API slug pattern and work identically with the REST API.


Conclusion

The WooCommerce REST API provides a powerful, well-documented interface for automating every stage of the order and inventory lifecycle. In this guide we covered:

  • Authentication — building a reusable Axios client with Basic Auth over HTTPS.
  • Order retrieval — filtering by status and date with automatic pagination.
  • Status updates — transitioning single or batched orders through the fulfilment workflow.
  • Order notes — logging automation events and notifying customers programmatically.
  • Refunds — issuing partial or full refunds with automatic inventory restocking.
  • Stock management — enabling manage_stock and setting quantities at the product and variation level.
  • Batch updates — efficiently syncing large catalogue stock changes in chunked requests.
  • Low-stock reporting — building real-time inventory alerts without a plugin.

The patterns shown here are intentionally composable. Drop the wooClient module into any Node.js backend, connect it to your warehouse system, ERP, or shipping carrier API, and you have the foundation for a fully automated WooCommerce operations layer. As your store grows, lean on the batch endpoints aggressively and consider caching read-heavy responses (e.g. product metadata) with Redis to stay well within the WooCommerce API rate limits.

Tags:#WooCommerce#REST API#Orders#Inventory Management
Dev Pattern Labs Engineering Team

Written by Dev Pattern LabsCore Engineering

Dev Pattern Labs is an engineering lab and digital consultancy specializing in custom Next.js web applications, WooCommerce REST API integrations, and workflow automation for growing businesses.

Advertisement

Recommended Companion Articles