Extending the WooCommerce REST API with Custom Routes and Fields

WooCommerce’s Store API (the replacement for the legacy wc/v2 endpoints) is extensible through woocommerce_store_api_register_endpoint_data(), while the admin REST API (wc/v3) can be extended with custom routes using the standard WordPress register_rest_route() approach inside the woocommerce_rest_api namespace.

Problem: WooCommerce's built-in REST API endpoints cover products, orders, and customers, but custom business objects — delivery zones, B2B accounts, or product configurators — have no corresponding API, forcing developers to use admin AJAX or custom wp-ajax actions.

Solution: Register a custom WooCommerce REST controller by extending WC_REST_Controller or WP_REST_Controller and hooking into woocommerce_rest_api_get_rest_namespaces. Define routes in register_routes(), implement get_item_schema() for documentation, and use the WooCommerce authentication middleware automatically by registering under the wc/v3 namespace.

The examples below add custom product meta to the wc/v3/products response, register a custom wc/v3 route for bulk stock updates, and use Application Passwords for REST API authentication.

get_data();
    $data['custom_fields'] = [
        'supplier_sku'  => get_post_meta( $product->get_id(), '_supplier_sku', true ),
        'lead_time_days'=> (int) get_post_meta( $product->get_id(), '_lead_time', true ),
        'is_featured'   => (bool) get_post_meta( $product->get_id(), '_is_featured_product', true ),
    ];
    $response->set_data( $data );
    return $response;
}, 10, 3 );

// Also handle CREATE/UPDATE — save incoming custom fields
add_action( 'woocommerce_rest_insert_product_object', function(
    WC_Product $product,
    WP_REST_Request $request,
    bool $creating
): void {
    $custom = $request->get_param( 'custom_fields' );
    if ( ! is_array( $custom ) ) return;

    if ( isset( $custom['supplier_sku'] ) ) {
        update_post_meta( $product->get_id(), '_supplier_sku', sanitize_text_field( $custom['supplier_sku'] ) );
    }
    if ( isset( $custom['lead_time_days'] ) ) {
        update_post_meta( $product->get_id(), '_lead_time', absint( $custom['lead_time_days'] ) );
    }
}, 10, 3 );

Register a custom bulk-update endpoint under the WooCommerce namespace:

 'POST',
        'callback'            => 'myplugin_bulk_update_stock',
        'permission_callback' => function(): bool {
            // WooCommerce uses wc_rest_check_post_permissions() internally
            return current_user_can( 'edit_products' );
        },
        'args' => [
            'updates' => [
                'type'     => 'array',
                'required' => true,
                'items'    => [
                    'type'       => 'object',
                    'properties' => [
                        'id'    => [ 'type' => 'integer', 'required' => true ],
                        'stock' => [ 'type' => 'integer', 'required' => true ],
                    ],
                ],
            ],
        ],
    ] );
} );

function myplugin_bulk_update_stock( WP_REST_Request $request ): WP_REST_Response {
    $updates  = $request->get_param( 'updates' );
    $results  = [];

    foreach ( $updates as $item ) {
        $product = wc_get_product( absint( $item['id'] ) );
        if ( ! $product ) {
            $results[] = [ 'id' => $item['id'], 'error' => 'Product not found' ];
            continue;
        }
        $product->set_stock_quantity( absint( $item['stock'] ) );
        $product->set_stock_status( $item['stock'] > 0 ? 'instock' : 'outofstock' );
        $product->save();
        $results[] = [ 'id' => $item['id'], 'stock' => $product->get_stock_quantity() ];
    }

    return new WP_REST_Response( [ 'updated' => $results ], 200 );
}

// ── AUTHENTICATION ──
// Use WordPress Application Passwords (Settings → Application Passwords in admin)
// Authorization: Basic base64(username:application_password)
// curl -X GET https://example.com/wp-json/wc/v3/products \
//      -u "admin:XXXX XXXX XXXX XXXX XXXX XXXX"

NOTE: WooCommerce uses its own permission system on top of WordPress capabilities. Always check permissions with current_user_can('edit_products') or wc_rest_check_post_permissions('product', 'create') in your permission callbacks rather than manage_options. Test your endpoints with wp rest wc-myplugin get --user=admin.

Leave Comment

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