How to Create a Custom WooCommerce REST API Endpoint

CodeAtoZ Engineer, CodeAtoZ
16 min read · 3028 words
Share: 𝕏 in
DEV DEEP DIVES · WOOCOMMERCE DEVELOPER TIPS #1

How to Create a WooCommerce Custom Endpoint — Complete PHP Guide

Build, authenticate and test your own WooCommerce REST API endpoint from scratch. Full working code included.

PHP 8+Requirement
WC 7+WooCommerce
~30minBuild time
Beginner+Difficulty

A WooCommerce custom endpoint lets you expose exactly the store data you need — nothing more, nothing less. Whether you’re building a mobile app, a headless React storefront, or syncing orders to an ERP, a WooCommerce custom endpoint gives you full control over the data shape, authentication, and business logic. This guide walks you through creating, securing, and testing a production-ready WooCommerce custom endpoint using register_rest_route().

WooCommerce ships with a comprehensive REST API covering products, orders, customers and more. But there are always situations where the built-in endpoints don’t quite fit — you need a combined response, a custom calculation, or access to data from your own plugin. That’s where a WooCommerce custom endpoint comes in.

In this tutorial, we’ll build a real-world example: a /wc/v3/store-summary endpoint that returns live sales stats. Every concept here applies to any WooCommerce custom endpoint you build, from simple GET routes to authenticated POST handlers.

PREREQUISITES

  • WordPress 6.0+ with WooCommerce 7.0+ installed
  • PHP 8.0 or higher
  • A child theme or custom plugin to add your code
  • Basic familiarity with PHP functions and WordPress hooks
  • Postman (or similar API client) for testing
woocommerce custom endpoint architecture — request flow from client through WordPress REST API to your handler by CodeAtoZ
WooCommerce custom endpoint request flow — from Postman/client through the WP REST router to your PHP callback

What Is a WooCommerce Custom Endpoint and When Do You Need One?

The WooCommerce REST API is built on top of the WordPress REST API. Every route is registered using register_rest_route(), which maps a URL namespace and path to a PHP callback function. When you create a WooCommerce custom endpoint, you’re doing exactly the same thing — adding your own route to the router.

You need a WooCommerce custom endpoint when:

  • You need a combined response that would require multiple API calls otherwise
  • Your plugin stores data not exposed by the built-in WooCommerce endpoints
  • You’re building a headless storefront that needs a bespoke data shape
  • You want to trigger server-side business logic (like generating a PDF invoice)
  • You’re integrating with a third-party system (ERP, CRM, fulfilment)

How register_rest_route() Works for a WooCommerce Custom Endpoint

The core function is register_rest_route(). It takes three arguments: a namespace (like wc/v3), a route pattern, and an array of arguments that define the methods, callback, and permissions.

PHP — Basic structure
add_action( 'rest_api_init', function () {
    register_rest_route(
        'wc/v3',                    // namespace
        '/store-summary',           // route path
        [
            'methods'             => WP_REST_Server::READABLE, // GET
            'callback'            => 'my_store_summary_callback',
            'permission_callback' => 'my_store_summary_permissions',
        ]
    );
} );

The namespace should match WooCommerce’s own (wc/v3) when your endpoint relates to WooCommerce data — this makes the URL intuitive: /wp-json/wc/v3/store-summary. For plugin-specific endpoints, use your own namespace like my-plugin/v1.

Step-by-Step: Building the WooCommerce Custom Endpoint

STEP 1 — Register the route

Hook into rest_api_init and call register_rest_route(). Place this in your plugin’s main PHP file or your theme’s functions.php.

PHP
add_action( 'rest_api_init', 'register_store_summary_endpoint' );

function register_store_summary_endpoint() {
    register_rest_route( 'wc/v3', '/store-summary', [
        [
            'methods'             => WP_REST_Server::READABLE,
            'callback'            => 'get_store_summary',
            'permission_callback' => 'store_summary_permission_check',
            'args'                => [
                'period' => [
                    'default'           => '30days',
                    'sanitize_callback' => 'sanitize_text_field',
                    'validate_callback' => function( $value ) {
                        return in_array( $value, [ '7days', '30days', 'year' ], true );
                    },
                ],
            ],
        ],
        [
            'methods'             => WP_REST_Server::CREATABLE,
            'callback'            => 'update_store_settings',
            'permission_callback' => 'store_admin_permission_check',
        ],
    ] );
}
STEP 2 — Add the permission callback

The permission callback runs before your main callback. It must return true to allow access or a WP_Error / false to block it. For WooCommerce endpoints, check for a valid consumer key using wc_rest_check_manager_permissions(), or use WordPress capabilities for simpler cases.

PHP
function store_summary_permission_check( WP_REST_Request $request ) {
    // Allow access to shop managers and administrators
    if ( current_user_can( 'manage_woocommerce' ) ) {
        return true;
    }

    // Also allow WooCommerce consumer key authentication
    if ( wc_rest_check_manager_permissions( 'reports', 'read' ) ) {
        return true;
    }

    return new WP_Error(
        'rest_forbidden',
        __( 'You do not have permission to access this endpoint.', 'your-plugin' ),
        [ 'status' => 403 ]
    );
}

function store_admin_permission_check( WP_REST_Request $request ) {
    return current_user_can( 'manage_options' );
}
STEP 3 — Write the callback and return a WP_REST_Response

The callback receives a WP_REST_Request object. Use its methods to read parameters, headers, and the request body. Always return a WP_REST_Response — never echo raw JSON.

PHP
function get_store_summary( WP_REST_Request $request ) {
    $period = $request->get_param( 'period' );

    // Calculate date range
    $date_query = [];
    if ( $period === '7days' ) {
        $date_query = [ 'after' => '7 days ago' ];
    } elseif ( $period === '30days' ) {
        $date_query = [ 'after' => '30 days ago' ];
    } elseif ( $period === 'year' ) {
        $date_query = [ 'after' => '1 year ago' ];
    }

    // Fetch orders
    $orders = wc_get_orders( [
        'status'     => [ 'completed', 'processing' ],
        'date_query' => $date_query,
        'limit'      => -1,
        'return'     => 'ids',
    ] );

    $total_orders   = count( $orders );
    $total_revenue  = 0;

    foreach ( $orders as $order_id ) {
        $order          = wc_get_order( $order_id );
        $total_revenue += (float) $order->get_total();
    }

    $data = [
        'period'         => $period,
        'total_orders'   => $total_orders,
        'total_revenue'  => round( $total_revenue, 2 ),
        'currency'       => get_woocommerce_currency(),
        'generated_at'   => current_time( 'c' ),
    ];

    return new WP_REST_Response( $data, 200 );
}
STEP 4 — Authentication with Consumer Key / Secret

WooCommerce’s built-in authentication uses consumer key/secret pairs. Create them under WooCommerce → Settings → Advanced → REST API. You can then authenticate via Basic Auth (HTTPS only) or OAuth 1.0a.

cURL — Basic Auth over HTTPS
curl -u "ck_xxxxxxxxxxxx:cs_xxxxxxxxxxxx" 
  "https://yourstore.com/wp-json/wc/v3/store-summary?period=30days"
JavaScript — fetch with Basic Auth
const ck = 'ck_xxxxxxxxxxxx';
const cs = 'cs_xxxxxxxxxxxx';
const base = btoa(`${ck}:${cs}`);

const response = await fetch(
  'https://yourstore.com/wp-json/wc/v3/store-summary?period=7days',
  {
    headers: {
      'Authorization': `Basic ${base}`,
      'Content-Type':  'application/json',
    }
  }
);
const data = await response.json();
console.log( data );
STEP 5 — Test with Postman

Open Postman, create a new GET request to https://yourstore.com/wp-json/wc/v3/store-summary. Under the Authorization tab, choose Basic Auth and enter your consumer key and secret. Add period=30days as a query parameter.

✅ A successful response returns HTTP 200 with your JSON payload. If you get 401, check your consumer key permissions are set to “Read” or higher. If you get 404, confirm you flushed permalinks: Settings → Permalinks → Save.

Handling POST Requests in Your WooCommerce Custom Endpoint

POST endpoints work the same way — you just change the method to WP_REST_Server::CREATABLE and read the body with $request->get_json_params().

PHP — POST handler example
function update_store_settings( WP_REST_Request $request ) {
    $params = $request->get_json_params();

    // Validate required field
    if ( empty( $params['low_stock_threshold'] ) ) {
        return new WP_Error(
            'missing_param',
            'low_stock_threshold is required.',
            [ 'status' => 400 ]
        );
    }

    $threshold = absint( $params['low_stock_threshold'] );
    update_option( 'woocommerce_notify_low_stock_amount', $threshold );

    return new WP_REST_Response(
        [ 'success' => true, 'threshold' => $threshold ],
        200
    );
}

Common Errors and Fixes for WooCommerce Custom Endpoints

ERROR REFERENCE

404 — Route not found

Fix: Go to Settings → Permalinks → Save Changes to flush the rewrite rules.

401 — Unauthorized

Fix: Consumer key doesn’t have the right permissions. Regenerate with Read/Write access in WooCommerce → Advanced → REST API.

403 — Forbidden

Fix: Your permission_callback is returning false. Check current_user_can() and make sure you’re sending the auth header.

rest_no_route — No route was found matching the URL and request method

Fix: Your namespace/route string has a typo, or the rest_api_init hook isn’t firing. Wrap in add_action(‘rest_api_init’) not init.

Response returns null or empty

Fix: You’re returning void from the callback. Always return a WP_REST_Response object, never echo or exit.

Full Working Plugin: WooCommerce Custom Endpoint

Below is the complete, production-ready plugin you can drop into /wp-content/plugins/my-wc-endpoint/ and activate immediately.

PHP — my-wc-endpoint.php (full plugin)
<?php
/**
 * Plugin Name: My WooCommerce Custom Endpoint
 * Description: Adds a /wc/v3/store-summary REST API endpoint.
 * Version:     1.0.0
 */

if ( ! defined( 'ABSPATH' ) ) exit;

add_action( 'rest_api_init', 'mywce_register_routes' );

function mywce_register_routes() {
    register_rest_route( 'wc/v3', '/store-summary', [
        [
            'methods'             => WP_REST_Server::READABLE,
            'callback'            => 'mywce_get_summary',
            'permission_callback' => 'mywce_check_permissions',
            'args'                => [
                'period' => [
                    'default'           => '30days',
                    'sanitize_callback' => 'sanitize_text_field',
                    'validate_callback' => fn( $v ) =>
                        in_array( $v, [ '7days', '30days', 'year' ], true ),
                ],
            ],
        ],
    ] );
}

function mywce_check_permissions() {
    return current_user_can( 'manage_woocommerce' )
        || wc_rest_check_manager_permissions( 'reports', 'read' );
}

function mywce_get_summary( WP_REST_Request $request ) {
    $period = $request->get_param( 'period' );
    $after  = match( $period ) {
        '7days' => '7 days ago',
        'year'  => '1 year ago',
        default => '30 days ago',
    };

    $orders        = wc_get_orders( [
        'status'       => [ 'completed', 'processing' ],
        'date_query'   => [ 'after' => $after ],
        'limit'        => -1,
        'return'       => 'ids',
    ] );

    $revenue = array_sum(
        array_map(
            fn( $id ) => (float) wc_get_order( $id )->get_total(),
            $orders
        )
    );

    return new WP_REST_Response( [
        'period'        => $period,
        'total_orders'  => count( $orders ),
        'total_revenue' => round( $revenue, 2 ),
        'currency'      => get_woocommerce_currency(),
        'generated_at'  => current_time( 'c' ),
    ], 200 );
}

Once activated, flush your permalinks and test the endpoint at /wp-json/wc/v3/store-summary?period=7days. For deeper WooCommerce REST API work, also see our guide on WooCommerce REST API authentication methods and our overview of WooCommerce development services.

Frequently Asked Questions About WooCommerce Custom Endpoints

What is a WooCommerce custom endpoint?

A WooCommerce custom endpoint is a URL route you register using register_rest_route() that maps to your own PHP callback function. It extends the WooCommerce REST API with logic specific to your store, plugin, or integration.

Do I need a WooCommerce custom endpoint for a headless store?

Usually yes — headless React or Next.js stores often need combined data responses or server-side logic that the built-in WooCommerce endpoints don’t provide. A custom endpoint lets you shape the API response exactly how your frontend needs it.

Should my WooCommerce custom endpoint use the wc/v3 namespace?

Use wc/v3 when your endpoint returns WooCommerce-specific data (orders, products, customers). Use your own namespace like myplugin/v1 for business logic or plugin-specific data. Mixing namespaces can confuse API consumers.

How do I authenticate requests to my WooCommerce custom endpoint?

The simplest method is Basic Auth over HTTPS using a consumer key and secret from WooCommerce → Settings → Advanced → REST API. For server-to-server calls, use OAuth 1.0a. For WordPress Application Passwords (WP 5.6+), check current_user_can() in your permission_callback.

Why does my WooCommerce custom endpoint return 404?

The most common cause is stale WordPress rewrite rules. Go to Settings → Permalinks and click Save Changes — this flushes the rules and registers your new route. Also confirm your add_action is hooked to rest_api_init, not init or plugins_loaded.

Can I add parameters to a WooCommerce custom endpoint?

Yes — use the args array in register_rest_route() to define query parameters with sanitize_callback and validate_callback functions. This keeps your endpoint secure and self-documenting. Parameters are accessible via $request->get_param(‘name’) in your callback.

Need a Custom WooCommerce Integration Built?

CodeAtoZ builds bespoke WooCommerce REST API integrations — headless storefronts, ERP syncs, mobile app backends and more. Let’s talk about your project.

CodeAtoZ
Lead Engineer & Founder, CodeAtoZ

10+ years building Magento 2 and WooCommerce platforms for high-volume e-commerce businesses. Writes about performance engineering, plugin architecture, and scalable commerce systems.

Join the Discussion

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