woocommerce-plugin-development

Create custom WooCommerce plugins using action/filter hooks, the Settings API, and REST API extensions to add features without modifying core

11 stars

Best use case

woocommerce-plugin-development is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Create custom WooCommerce plugins using action/filter hooks, the Settings API, and REST API extensions to add features without modifying core

Teams using woocommerce-plugin-development should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/woocommerce-plugin-development/SKILL.md --create-dirs "https://raw.githubusercontent.com/finsilabs/awesome-ecommerce-skills/main/skills/platform-woocommerce/woocommerce-plugin-development/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/woocommerce-plugin-development/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How woocommerce-plugin-development Compares

Feature / Agentwoocommerce-plugin-developmentStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Create custom WooCommerce plugins using action/filter hooks, the Settings API, and REST API extensions to add features without modifying core

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# WooCommerce Plugin Development

## Overview

Build custom WooCommerce plugins that extend store functionality using WordPress hooks (actions and filters), the WooCommerce Settings API, custom post types and meta boxes, REST API extensions, and HPOS (High-Performance Order Storage) compatibility. This skill covers plugin architecture, the WooCommerce lifecycle hooks for orders and products, and patterns for building maintainable, upgrade-safe extensions.

## When to Use This Skill

- When building a custom feature that extends WooCommerce (custom shipping, payment, or discount logic)
- When creating a plugin that adds admin settings and configuration pages
- When hooking into the WooCommerce checkout, order, or product lifecycle
- When extending the WooCommerce REST API with custom endpoints
- When migrating a plugin to support HPOS (High-Performance Order Storage)

## Core Instructions

1. **Set up the plugin boilerplate**

   ```php
   <?php
   /**
    * Plugin Name: My Custom WooCommerce Extension
    * Description: Adds custom functionality to WooCommerce
    * Version: 1.0.0
    * Author: Your Name
    * Requires Plugins: woocommerce
    * WC requires at least: 8.0
    * WC tested up to: 9.0
    *
    * @package MyWooExtension
    */

   defined('ABSPATH') || exit;

   // Check if WooCommerce is active
   if (!in_array('woocommerce/woocommerce.php', apply_filters('active_plugins', get_option('active_plugins')))) {
       return;
   }

   define('MWE_VERSION', '1.0.0');
   define('MWE_PLUGIN_DIR', plugin_dir_path(__FILE__));
   define('MWE_PLUGIN_URL', plugin_dir_url(__FILE__));

   // Declare HPOS compatibility
   add_action('before_woocommerce_init', function () {
       if (class_exists(\Automattic\WooCommerce\Utilities\FeaturesUtil::class)) {
           \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility(
               'custom_order_tables',
               __FILE__,
               true
           );
       }
   });

   // Initialize the plugin after WooCommerce loads
   add_action('woocommerce_loaded', function () {
       require_once MWE_PLUGIN_DIR . 'includes/class-mwe-main.php';
       MWE_Main::instance();
   });
   ```

2. **Use WooCommerce hooks for order lifecycle events**

   ```php
   class MWE_Order_Handler {
       public function __construct() {
           // Order status transitions
           add_action('woocommerce_order_status_completed', [$this, 'on_order_completed'], 10, 2);
           add_action('woocommerce_order_status_changed', [$this, 'on_status_change'], 10, 4);

           // New order created
           add_action('woocommerce_checkout_order_created', [$this, 'on_order_created']);

           // Payment complete
           add_action('woocommerce_payment_complete', [$this, 'on_payment_complete']);

           // Before/after order item saved (HPOS compatible)
           add_action('woocommerce_new_order_item', [$this, 'on_new_order_item'], 10, 3);
       }

       public function on_order_completed(int $order_id, \WC_Order $order): void {
           // Example: trigger fulfillment via external API
           $items = $order->get_items();
           $shipping_address = $order->get_address('shipping');

           foreach ($items as $item) {
               $product = $item->get_product();
               $sku = $product->get_sku();
               $quantity = $item->get_quantity();

               // Call your external fulfillment service
               $this->send_to_fulfillment($sku, $quantity, $shipping_address);
           }

           $order->add_order_note('Order sent to fulfillment service.');
       }

       public function on_status_change(int $order_id, string $from, string $to, \WC_Order $order): void {
           // Log status transitions
           error_log(sprintf(
               'Order #%d transitioned from %s to %s',
               $order_id, $from, $to
           ));
       }
   }
   ```

3. **Add settings using the WooCommerce Settings API**

   ```php
   class MWE_Settings {
       public function __construct() {
           add_filter('woocommerce_settings_tabs_array', [$this, 'add_settings_tab'], 50);
           add_action('woocommerce_settings_tabs_mwe_settings', [$this, 'render_settings']);
           add_action('woocommerce_update_options_mwe_settings', [$this, 'save_settings']);
       }

       public function add_settings_tab(array $tabs): array {
           $tabs['mwe_settings'] = __('My Extension', 'my-woo-extension');
           return $tabs;
       }

       public function render_settings(): void {
           woocommerce_admin_fields($this->get_settings());
       }

       public function save_settings(): void {
           woocommerce_update_options($this->get_settings());
       }

       private function get_settings(): array {
           return [
               [
                   'title' => __('General Settings', 'my-woo-extension'),
                   'type'  => 'title',
                   'id'    => 'mwe_general_settings',
               ],
               [
                   'title'    => __('Enable Feature', 'my-woo-extension'),
                   'desc'     => __('Enable the custom feature', 'my-woo-extension'),
                   'id'       => 'mwe_enable_feature',
                   'type'     => 'checkbox',
                   'default'  => 'yes',
               ],
               [
                   'title'    => __('API Key', 'my-woo-extension'),
                   'desc'     => __('Enter your external service API key', 'my-woo-extension'),
                   'id'       => 'mwe_api_key',
                   'type'     => 'text',
                   'css'      => 'min-width: 300px;',
               ],
               [
                   'title'    => __('Processing Mode', 'my-woo-extension'),
                   'id'       => 'mwe_processing_mode',
                   'type'     => 'select',
                   'options'  => [
                       'sync'  => __('Synchronous', 'my-woo-extension'),
                       'async' => __('Asynchronous (Queue)', 'my-woo-extension'),
                   ],
                   'default'  => 'sync',
               ],
               [
                   'type' => 'sectionend',
                   'id'   => 'mwe_general_settings',
               ],
           ];
       }
   }
   ```

4. **Modify product data with filters**

   ```php
   class MWE_Product_Customizer {
       public function __construct() {
           // Add a custom product tab in admin
           add_filter('woocommerce_product_data_tabs', [$this, 'add_product_tab']);
           add_action('woocommerce_product_data_panels', [$this, 'render_product_panel']);
           add_action('woocommerce_process_product_meta', [$this, 'save_product_meta']);

           // Modify price display on frontend
           add_filter('woocommerce_get_price_html', [$this, 'modify_price_display'], 10, 2);

           // Add custom data to cart item
           add_filter('woocommerce_add_cart_item_data', [$this, 'add_custom_cart_data'], 10, 3);

           // Display custom data in cart
           add_filter('woocommerce_get_item_data', [$this, 'display_cart_item_data'], 10, 2);

           // Save custom data to order item
           add_action('woocommerce_checkout_create_order_line_item', [$this, 'save_order_item_meta'], 10, 4);
       }

       public function add_product_tab(array $tabs): array {
           $tabs['mwe_custom'] = [
               'label'    => __('Custom Fields', 'my-woo-extension'),
               'target'   => 'mwe_custom_product_data',
               'class'    => [],
               'priority' => 80,
           ];
           return $tabs;
       }

       public function render_product_panel(): void {
           global $post;
           echo '<div id="mwe_custom_product_data" class="panel woocommerce_options_panel">';

           wp_nonce_field('mwe_save_product_meta', 'mwe_product_meta_nonce');

           woocommerce_wp_text_input([
               'id'          => '_mwe_custom_field',
               'label'       => __('Custom Field', 'my-woo-extension'),
               'description' => __('Enter a custom value for this product', 'my-woo-extension'),
               'desc_tip'    => true,
           ]);

           woocommerce_wp_select([
               'id'      => '_mwe_product_badge',
               'label'   => __('Product Badge', 'my-woo-extension'),
               'options' => [
                   ''         => __('None', 'my-woo-extension'),
                   'new'      => __('New', 'my-woo-extension'),
                   'sale'     => __('Sale', 'my-woo-extension'),
                   'limited'  => __('Limited Edition', 'my-woo-extension'),
               ],
           ]);

           echo '</div>';
       }

       public function save_product_meta(int $post_id): void {
           // Verify nonce before processing $_POST data
           if (!isset($_POST['mwe_product_meta_nonce']) ||
               !wp_verify_nonce($_POST['mwe_product_meta_nonce'], 'mwe_save_product_meta')) {
               return;
           }

           $custom_field = sanitize_text_field($_POST['_mwe_custom_field'] ?? '');
           update_post_meta($post_id, '_mwe_custom_field', $custom_field);

           $badge = sanitize_text_field($_POST['_mwe_product_badge'] ?? '');
           update_post_meta($post_id, '_mwe_product_badge', $badge);
       }
   }
   ```

5. **Extend the WooCommerce REST API**

   ```php
   class MWE_REST_Controller {
       public function __construct() {
           add_action('rest_api_init', [$this, 'register_routes']);
       }

       public function register_routes(): void {
           register_rest_route('mwe/v1', '/analytics/summary', [
               'methods'             => \WP_REST_Server::READABLE,
               'callback'            => [$this, 'get_analytics_summary'],
               'permission_callback' => [$this, 'check_admin_permission'],
           ]);

           register_rest_route('mwe/v1', '/products/(?P<id>\d+)/custom-data', [
               'methods'             => \WP_REST_Server::READABLE,
               'callback'            => [$this, 'get_product_custom_data'],
               'permission_callback' => '__return_true',
               'args'                => [
                   'id' => [
                       'validate_callback' => function ($param) {
                           return is_numeric($param);
                       },
                   ],
               ],
           ]);
       }

       public function check_admin_permission(\WP_REST_Request $request): bool {
           return current_user_can('manage_woocommerce');
       }

       public function get_analytics_summary(\WP_REST_Request $request): \WP_REST_Response {
           $days = absint($request->get_param('days') ?? 30);
           $date_from = date('Y-m-d', strtotime("-{$days} days"));

           // HPOS-compatible order query
           $orders = wc_get_orders([
               'date_created' => ">={$date_from}",
               'status'       => ['wc-completed', 'wc-processing'],
               'limit'        => -1,
               'return'       => 'ids',
           ]);

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

           return new \WP_REST_Response([
               'period'        => "{$days}_days",
               'total_orders'  => count($orders),
               'total_revenue' => $total_revenue,
               'avg_order'     => count($orders) > 0 ? $total_revenue / count($orders) : 0,
           ]);
       }
   }
   ```

6. **Create a custom shipping method**

   ```php
   // Register the shipping method
   add_filter('woocommerce_shipping_methods', function (array $methods): array {
       $methods['mwe_custom_shipping'] = 'MWE_Custom_Shipping_Method';
       return $methods;
   });

   class MWE_Custom_Shipping_Method extends \WC_Shipping_Method {
       public function __construct(int $instance_id = 0) {
           $this->id                 = 'mwe_custom_shipping';
           $this->instance_id        = absint($instance_id);
           $this->method_title       = __('Custom Shipping', 'my-woo-extension');
           $this->method_description = __('Custom shipping rate calculation', 'my-woo-extension');
           $this->supports           = ['shipping-zones', 'instance-settings'];

           $this->init();
       }

       public function init(): void {
           $this->init_form_fields();
           $this->init_settings();

           $this->title = $this->get_option('title', 'Custom Shipping');
           $this->enabled = $this->get_option('enabled', 'yes');
       }

       public function init_form_fields(): void {
           $this->instance_form_fields = [
               'title' => [
                   'title'   => __('Method Title', 'my-woo-extension'),
                   'type'    => 'text',
                   'default' => 'Custom Shipping',
               ],
               'base_cost' => [
                   'title'   => __('Base Cost', 'my-woo-extension'),
                   'type'    => 'price',
                   'default' => '5.00',
               ],
               'per_item_cost' => [
                   'title'   => __('Per Item Cost', 'my-woo-extension'),
                   'type'    => 'price',
                   'default' => '1.00',
               ],
           ];
       }

       public function calculate_shipping($package = []): void {
           $base_cost = (float) $this->get_option('base_cost', 5);
           $per_item = (float) $this->get_option('per_item_cost', 1);

           $item_count = 0;
           foreach ($package['contents'] as $item) {
               $item_count += $item['quantity'];
           }

           $cost = $base_cost + ($per_item * $item_count);

           $this->add_rate([
               'id'    => $this->get_rate_id(),
               'label' => $this->title,
               'cost'  => $cost,
           ]);
       }
   }
   ```

## Examples

### Custom WooCommerce email notification

```php
class MWE_Custom_Email extends \WC_Email {
    public function __construct() {
        $this->id             = 'mwe_order_shipped';
        $this->title          = __('Order Shipped', 'my-woo-extension');
        $this->description    = __('Sent when an order is marked as shipped', 'my-woo-extension');
        $this->heading        = __('Your order has shipped!', 'my-woo-extension');
        $this->subject        = __('Your {site_title} order #{order_number} has shipped', 'my-woo-extension');
        $this->template_base  = MWE_PLUGIN_DIR . 'templates/';
        $this->template_html  = 'emails/order-shipped.php';
        $this->template_plain = 'emails/plain/order-shipped.php';
        $this->customer_email = true;

        add_action('mwe_order_shipped_notification', [$this, 'trigger'], 10, 2);

        parent::__construct();
    }

    public function trigger(int $order_id, string $tracking_number): void {
        $this->object = wc_get_order($order_id);
        if (!$this->object) return;

        $this->recipient = $this->object->get_billing_email();
        $this->placeholders['{tracking_number}'] = $tracking_number;

        if ($this->is_enabled() && $this->get_recipient()) {
            $this->send(
                $this->get_recipient(),
                $this->get_subject(),
                $this->get_content(),
                $this->get_headers(),
                $this->get_attachments()
            );
        }
    }
}

// Register the email class
add_filter('woocommerce_email_classes', function (array $emails): array {
    $emails['MWE_Order_Shipped'] = new MWE_Custom_Email();
    return $emails;
});
```

### HPOS-compatible order meta access

```php
// Old way (post meta) — DEPRECATED
// $value = get_post_meta($order_id, '_custom_field', true);

// New way (HPOS compatible)
$order = wc_get_order($order_id);

// Read custom meta
$custom_value = $order->get_meta('_mwe_custom_field', true);

// Write custom meta
$order->update_meta_data('_mwe_custom_field', 'new_value');
$order->save();

// Query orders with custom meta (HPOS compatible)
$orders = wc_get_orders([
    'meta_query' => [
        [
            'key'   => '_mwe_custom_field',
            'value' => 'specific_value',
        ],
    ],
    'status' => 'completed',
    'limit'  => 50,
]);
```

## Best Practices

- **Always declare HPOS compatibility** — WooCommerce is migrating to custom order tables; use `FeaturesUtil::declare_compatibility` and avoid direct `wp_posts` queries for orders
- **Use WooCommerce CRUD methods** — access order/product data via `$order->get_total()`, not `get_post_meta()`; CRUD methods work with both legacy and HPOS storage
- **Hook at the right priority** — default priority is 10; use lower numbers (5) to run before WooCommerce's own handlers, higher (20+) to run after
- **Sanitize and escape everything** — use `sanitize_text_field()` on input, `esc_html()` / `esc_attr()` on output; never trust `$_POST` or `$_GET` data
- **Use WooCommerce's built-in functions** — `wc_get_orders()`, `wc_get_products()`, `wc_price()` handle edge cases and are forward-compatible
- **Namespace your meta keys** — prefix all custom meta with your plugin slug (e.g., `_mwe_`) to avoid conflicts with other plugins
- **Support multisite** — use `is_plugin_active_for_network()` checks if your plugin needs to work on WordPress multisite
- **Test with WooCommerce's test suite** — use `WC_Unit_Test_Case` as a base class for PHPUnit tests

## Common Pitfalls

| Problem | Solution |
|---------|----------|
| Plugin breaks after WooCommerce update | Hook into `woocommerce_loaded` instead of `plugins_loaded` to ensure WooCommerce classes are available |
| Order meta not saving with HPOS enabled | Use `$order->update_meta_data()` and `$order->save()` instead of `update_post_meta()` |
| Hooks fire multiple times (duplicate emails, double stock reduction) | Check if the hook has already been processed using a static flag or transient; use `did_action()` to check |
| Custom shipping method not appearing | Ensure the method is registered via `woocommerce_shipping_methods` filter and the class extends `WC_Shipping_Method` |
| REST API endpoint returns 403 | Check `permission_callback` — use `current_user_can('manage_woocommerce')` for admin endpoints, or `'__return_true'` for public |

## Related Skills

- @woocommerce-performance
- @product-data-modeling
- @discount-engine
- @ecommerce-seo
- @erp-integration

Related Skills

woocommerce-subscriptions

11
from finsilabs/awesome-ecommerce-skills

Add subscription products to WooCommerce with automatic recurring billing, renewal notifications, and subscriber self-service management

woocommerce-rest-api

11
from finsilabs/awesome-ecommerce-skills

Integrate or build headless frontends on WooCommerce using its REST API for products, orders, customers, and coupons with key authentication

woocommerce-performance

11
from finsilabs/awesome-ecommerce-skills

Fix slow WooCommerce stores by optimizing database queries, clearing transients, enabling Redis object caching, and configuring page caching

woocommerce-blocks

11
from finsilabs/awesome-ecommerce-skills

Customize WooCommerce checkout and cart pages using Gutenberg blocks with server-side rendering, slot-fills, and extensibility hooks

shopify-theme-development

11
from finsilabs/awesome-ecommerce-skills

Build and customize Shopify themes using Liquid templating, JSON sections, dynamic blocks, and theme app extensions for added functionality

sfcc-cartridge-development

11
from finsilabs/awesome-ecommerce-skills

Build SFRA-based Salesforce Commerce Cloud cartridges with controllers, ISML templates, and hooks to customize storefront behavior

magento-module-development

11
from finsilabs/awesome-ecommerce-skills

Build custom Magento 2 modules using dependency injection, plugins, observers, and service contracts to extend core functionality cleanly

saleor-development

11
from finsilabs/awesome-ecommerce-skills

Build and extend Saleor's GraphQL-based headless commerce platform with custom apps, webhook handlers, and dashboard UI customizations

medusa-development

11
from finsilabs/awesome-ecommerce-skills

Extend the open-source Medusa commerce platform with custom services, event subscribers, and API endpoints for unique business requirements

wishlist-save-for-later

11
from finsilabs/awesome-ecommerce-skills

Let shoppers save products to a wishlist, share it with friends, and get notified when saved items come back in stock or drop in price

storefront-theming

11
from finsilabs/awesome-ecommerce-skills

Build a themeable storefront with design tokens and CSS custom properties that supports white-labeling, multi-brand variants, and dark mode

search-autocomplete

11
from finsilabs/awesome-ecommerce-skills

Speed up product discovery with instant search suggestions, fuzzy typo matching, and category-aware results powered by Algolia or Elasticsearch