woocommerce-plugin-development について
WooCommerce プラグイン開発者向けのスキルです。ストア機能、API、注文ストレージを対象に、拡張設計とアップグレードを考慮した実装方法を扱います。
主な機能
- 01プラグインの設計と基本構成
- 02注文・商品ライフサイクルフック
- 03WooCommerce 設定ページ
- 04REST API ルートの拡張
- 05HPOS 対応の注文データアクセス
活用シーン
- 01カスタム配送・決済・割引ロジックの追加
- 02管理設定・構成ページの作成
- 03チェックアウト・注文・商品動作の拡張
- 04HPOS 対応に向けたプラグイン移行
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ finsilabs/ awesome-ecommerce-skills --skill woocommerce-plugin-developmentインストール後、Claude Code で/woocommerce-plugin-developmentを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
tile.jsonevals/custom-shipping-method-and-rest-api-endp/criteria.jsontask.mdevals/plugin-boilerplate-hpos-compatibility-or/criteria.jsontask.mdevals/woocommerce-settings-api-and-product-cus/criteria.jsontask.md詳細
WooCommerce プラグイン開発
概要
WordPress フック(アクションとフィルター)、WooCommerce Settings API、カスタム投稿タイプとメタボックス、REST API の拡張、HPOS(High-Performance Order Storage)への対応を使用して、ストアの機能を拡張するカスタム WooCommerce プラグインを構築します。このスキルでは、プラグインのアーキテクチャ、注文と商品の WooCommerce ライフサイクルフック、保守しやすくアップグレードに安全な拡張機能を構築するためのパターンを扱います。
このスキルを使用する場面
- WooCommerce を拡張するカスタム機能(カスタム配送、決済、割引ロジックなど)を構築するとき
- 管理設定や設定ページを追加するプラグインを作成するとき
- WooCommerce のチェックアウト、注文、商品ライフサイクルにフックするとき
- カスタムエンドポイントを使用して WooCommerce REST API を拡張するとき
- プラグインを HPOS(High-Performance Order Storage)に対応させるとき
主な手順
-
プラグインのひな形を設定する
<?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(); }); -
注文のライフサイクルイベントに WooCommerce フックを使用する
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 )); } } -
WooCommerce Settings API を使用して設定を追加する
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', ], ]; } } -
フィルターを使用して商品データを変更する
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); } } -
WooCommerce REST API を拡張する
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, ]); } } -
カスタム配送方法を作成する
// 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, ]); } }
例
カスタム WooCommerce メール通知
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 に対応した注文メタデータへのアクセス
// 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,
]);
ベストプラクティス
- HPOS への対応を必ず宣言する — WooCommerce はカスタム注文テーブルへの移行を進めています。
FeaturesUtil::declare_compatibilityを使用し、注文に対するwp_postsの直接クエリは避けてください - WooCommerce の CRUD メソッドを使用する — 注文や商品のデータには
get_post_meta()ではなく$order->get_total()などを使用してアクセスしてください。CRUD メソッドは従来のストレージと HPOS の両方で動作します - 適切な優先度でフックする — デフォルトの優先度は 10 です。WooCommerce のハンドラーより前に実行するには小さい値(5)を、後に実行するには大きい値(20 以上)を使用してください
- すべてをサニタイズし、エスケープする — 入力には
sanitize_text_field()を、出力にはesc_html()/esc_attr()を使用してください。$_POSTや$_GETのデータを決して信頼しないでください - WooCommerce の組み込み関数を使用する —
wc_get_orders()、wc_get_products()、wc_price()はエッジケースに対応し、将来のバージョンとの互換性も確保します - メタキーに名前空間を使用する — 他のプラグインとの競合を避けるため、カスタムメタキーにはすべてプラグインのスラッグを接頭辞として付けてください(例:
_mwe_) - マルチサイトに対応する — プラグインを WordPress マルチサイトで動作させる必要がある場合は、
is_plugin_active_for_network()によるチェックを使用してください - WooCommerce のテストスイートでテストする — PHPUnit テストの基底クラスとして
WC_Unit_Test_Caseを使用してください
よくある落とし穴
| 問題 | 解決策 |
|---|---|
| WooCommerce の更新後にプラグインが動作しなくなる | WooCommerce のクラスが利用可能であることを確認するため、plugins_loaded ではなく woocommerce_loaded にフックします |
| HPOS 有効時に注文メタデータが保存されない | update_post_meta() ではなく $order->update_meta_data() と $order->save() を使用します |
| フックが複数回実行される(メールの重複送信、在庫の二重減算) | 静的フラグまたはトランジェントを使用して、フックがすでに処理されたか確認します。did_action() を使って確認してください |
| カスタム配送方法が表示されない | woocommerce_shipping_methods フィルターでその方法が登録されていること、およびクラスが WC_Shipping_Method を継承していることを確認します |
| REST API エンドポイントが 403 を返す | permission_callback を確認します。管理者向けエンドポイントには current_user_can('manage_woocommerce') を、公開エンドポイントには '__return_true' を使用してください |
関連スキル
- @woocommerce-performance
- @product-data-modeling
- @discount-engine
- @ecommerce-seo
- @erp-integration
コメント
Markdown 対応