Creative Andrew’s Blog

Web development articles focused on WordPress and headless applications.

WooCommerce Store API: Adding Custom Data

  • Rest API
  • Store API
  • WooCommerce
  • WordPress


In this tutorial, we will explore how to extend the WooCommerce Store API and enhance your store's API response by adding custom data. We will provide step-by-step instructions and code examples to help you implement this functionality efficiently. Let's dive into the process of extending the WooCommerce Store API and incorporating custom data into your store's API response.

WordPress and WooCommerce provide a variety of REST APIs that can be utilized for both traditional and decoupled applications. Among the most popular are the WordPress REST API, which has been included in the core since version 4.4, and the WooCommerce REST API v3.

However, with the advent of Gutenberg and the world of blocks, the WooCommerce REST API v3 did not seem to meet the demands of the new JavaScript technologies and methods. This was primarily due to the fact that it required authentication to consume its endpoints and lacked several functionalities needed for customer-facing requirements, such as cart, checkout, and filter count calculations.

Enters WooCommerce Store API

This is where the WooCommerce Store API comes into play. According to its documentation, it provides "public REST API endpoints for the development of customer-facing cart, checkout, and product functionality. It follows many of the patterns used in the WordPress REST API."

One of the most useful endpoints I have come across is the product collection, which enables the retrieval of aggregated data such as attribute counts, minimum and maximum prices, and so on.

Here is an example of a request taken from their documentation that can be used to obtain the count of products matching the pa_color attribute, along with its corresponding response.

GET /products/collection-data?calculate_attribute_counts[0][query_type]=or&calculate_attribute_counts[0][taxonomy]=pa_color

{
 "attribute_counts": [ 
  {
   "term": 22,
   "count": 4
  },
 ],
}

How to extend the WooCommerce Store API

To extend the WooCommerce Store API, we need to understand that not all endpoints can be customized to include our data. Therefore, we must verify if the desired endpoint is available for extension.

In this case, we will be focusing on the wp-json/wc/store/v1/products/ endpoint, which can be easily extended without issues.

By making a standard call to to it, we will receive the following response:

[
 {
  "id": 34,
  "name": "WordPress Pennant",
  "variation": "",
  "permalink": "https://local.wordpress.test/product/wordpress-pennant/", 
  "sku": "wp-pennant",
  "summary": "<p>This is an external product.</p>",
  "short_description": "<p>This is an external product.</p>",
  "description": "<p>Pellentesque habitant morbi tristique.</p>",
  "on_sale": false, 
  "average_rating": "0",
  "review_count": 0,
  "has_options": false,
  "is_purchasable": true,
  ...
 }
]

Adding a new field to the products endpoint

To add a custom meta field to the products endpoint, we can utilize the ExtendSchema helper class provided by WooCommerce, which includes all the necessary methods to perform this task.

However, we must also leverage the newly introduced container with dependency injection, which is only available after the woocommerce_blocks_loaded action hook has been fired.

To access the container and pass it into our own class, WooCommerceProductExtendStore, we can use the following approach.

add_action('woocommerce_blocks_loaded', function () {
  $extend = StoreApi::container()->get(ExtendSchema::class);
  (new WooCommerceProductExtendStoreEndpoint())->init($extend);
});

The starting point of our class can look like this:

/**
 * WooCommerce Product Extend Store API.
 *
 * A class to extend the store public API with product related data.
 */
class WooCommerceProductExtendStoreEndpoint {

  /**
   * Stores Rest Extending instance.
   *
   * @var ExtendSchema
   */
  private $extend;

  /**
   * Plugin identifier, unique to each plugin.
   *
   * @var string
   */
  const IDENTIFIER = 'custom_product_data';

  /**
   * Initializes the class and hooks required data.
   *
   * @param ExtendSchema $extend_rest_api
   *   An instance of the ExtendSchema class.
   */
  public function init(ExtendSchema $extend_rest_api) {
    $this->extend = $extend_rest_api;
    $this->extend_store();
  }

}

Let's now define extend_store method which will be responsible for setting up everything we need to add our custom metadata to the API response.

/**
 * Registers the actual data into each endpoint.
 */
public function extend_store() {
  // Register into `store/v1/products/`
  $this->extend->register_endpoint_data([
    'endpoint' => ProductSchema::IDENTIFIER,
    'namespace' => self::IDENTIFIER,
    'data_callback' => [$this, 'extend_product_data'],
    'schema_callback' => [$this, 'extend_product_schema'],
  ]);
}

In this method, we should define the endpoint we want to extend, the namespace so that it can be easily differentiated from other code that might also be extending the API, and two callbacks - one for the data itself and one for the schema.

The first callback, extend_product_data, will be responsible for adding the data to the response. Here's an example of how it can be defined:

/**
 * Registers custom product meta into product response endpoint.
 *
 * @param WC_Product $product
 *   Current product.
 *
 * @return array
 *   Array with the custom meta value.
 */
public  function extend_product_data(\WC_Product $product):array {
  $custom_meta = get_post_meta($product->id, 'my_custom_meta', true);
  return ['my_custom_meta' => $custom_meta ?? ''];
}

The second callback, schema_callback, is responsible for registering the shape of our data:

/**
 * Registers custom product schema into schema endpoint.
 *
 * @return array
 *   Registered schema.
 */
public  function extend_product_schema():array {
  return [
    'my_custom_meta' => [
      'description' => __('My custom data', 'plugin-namespace'),
      'type' => 'string',
      'readonly' => TRUE,
    ],
  ];
}

Once we have defined have done this, the Store API response should now include a new field called extension that contains our registered property. This new field will use our identifier and the name provided in our registered schema callback.

[
 {
  "id": 34,
  "name": "WordPress Pennant",
  "variation": "",
  "permalink": "https://local.wordpress.test/product/wordpress-pennant/", 
  "sku": "wp-pennant",
  "summary": "<p>This is an external product.</p>",
  "short_description": "<p>This is an external product.</p>",
  "description": "<p>Pellentesque habitant morbi tristique.</p>",
  "on_sale": false, 
  "average_rating": "0",
  "review_count": 0,
  "has_options": false,
  "is_purchasable": true,
  "extensions": {
     "custom_product_data": {
        "my_custom_meta": "my_custom_meta_value"
      }
  }
 }
]

Putting all together:

use Automattic\WooCommerce\StoreApi\StoreApi;
use Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema;
use Automattic\WooCommerce\StoreApi\Schemas\V1\ProductSchema;

/**
 * WooCommerce Product Extend Store API.
 *
 * A class to extend the store public API with product related data
 */
class WooCommerceProductExtendStoreEndpoint {

  /**
   * Stores Rest Extending instance.
   *
   * @var ExtendSchema
   */
  private $extend;

  /**
   * Plugin Identifier, unique to each plugin.
   *
   * @var string
   */
  const IDENTIFIER = 'custom_product_data';

  /**
   * Initializes the class and hooks required data.
   *
   * @param ExtendSchema $extend_rest_api
   *   An instance of the ExtendSchema class.
   */
  public function init(ExtendSchema $extend_rest_api) {
    $this->extend = $extend_rest_api;
    $this->extend_store();
  }

  /**
   * Registers the actual data into each endpoint.
   */
  public function extend_store() {
    // Register into `cart/items`
    $this->extend->register_endpoint_data([
      'endpoint' => ProductSchema::IDENTIFIER,
      'namespace' => self::IDENTIFIER,
      'data_callback' => [$this, 'extend_product_data'],
      'schema_callback' => [$this, 'extend_product_schema'],
    ]);
  }

  /**
   * Registers custom product meta into product response endpoint.
   *
   * @param WC_Product $product
   *   Current product item data.
   *
   * @return array
   */
  public  function extend_product_data(\WC_Product $product):array {
    $custom_meta = get_post_meta($product->id, 'my_custom_meta', true);
    return ['my_custom_meta' => $custom_meta ?? ''];
  }

  /**
   * Registers custom product schema into schema endpoint.
   *
   * @return array
   *   Registered schema.
   */
  public  function extend_product_schema():array {
    return [
      'my_custom_meta' => [
        'description' => __('My custom data', 'plugin-namespace'),
        'type' => 'string',
        'readonly' => TRUE,
      ],
    ];
  }

}

/**
 * Initialize the plugin.
 */
add_action('woocommerce_blocks_loaded', function () {
  $extend = StoreApi::container()->get(ExtendSchema::class);
  (new WooCommerceProductExtendStoreEndpoint())->init($extend);
});

In summary, by extending the existing WooCommerce Store API, you can efficiently incorporate custom data into your store's API response, without having to create a separate API endpoint from scratch. By leveraging the ExtendSchema helper class and the new container with dependency injection, we can easily include our custom meta fields or any other data in the API response.

Remember to check if the endpoint you want to extend is eligible for enhancement before starting your development process. Also, always keep in mind the performance implications of adding custom data to the API response, as it may affect the overall performance of your store.