2026年7月28日

2026年7月28日

WooCommerceにカスタム商品タイプを作成する方法

はじめに

WooCommerceの標準商品タイプ(シンプル・バリエーション・グループ・外部リンク)では対応できないビジネスロジックを持つ場合、カスタム商品タイプを作成することで独自の商品管理が可能になります。本記事では WC_Product クラスの継承から管理画面へのフィールド追加・保存処理まで、実践的な手順をコード例とともに解説します。

症状・原因

  • 定期購読・レンタル・予約など標準商品タイプでは表現できない商品がある
  • カスタム商品タイプの作成方法が複雑でドキュメントが散在している
  • 商品編集画面に独自フィールドを追加したい
  • カスタム商品タイプのデータを効率よく保存・取得したい
  • WooCommerce のアップデートでカスタム商品タイプが壊れてしまう

解決手順

ステップ1:woocommerce_product_class フィルターでカスタム商品タイプを登録する

<?php
// mu-plugins/custom-product-type.php または プラグインのメインファイル

// WooCommerce が使用するクラスを登録する
add_filter( 'woocommerce_product_class', 'register_custom_product_class', 10, 2 );

function register_custom_product_class( $classname, $product_type ) {
    // 'subscription' が商品タイプの識別子(スラッグ)
    if ( 'subscription' === $product_type ) {
        $classname = 'WC_Product_Subscription';
    }
    return $classname;
}

// 商品タイプを WooCommerce に登録する
add_filter( 'product_type_selector', 'add_custom_product_type_to_selector' );

function add_custom_product_type_to_selector( $types ) {
    // 商品編集画面のドロップダウンに追加
    $types['subscription'] = __( 'サブスクリプション商品', 'my-plugin' );
    return $types;
}

// WooCommerce の初期化後にクラスファイルを読み込む
add_action( 'plugins_loaded', 'load_custom_product_type_class' );

function load_custom_product_type_class() {
    if ( class_exists( 'WC_Product' ) ) {
        require_once plugin_dir_path( __FILE__ ) . 'class-wc-product-subscription.php';
    }
}

ステップ2:WC_Product を継承したカスタムクラスを作成する

<?php
// class-wc-product-subscription.php

/**
 * カスタム商品タイプ:サブスクリプション商品
 * WC_Product を継承して独自のビジネスロジックを実装する
 */
class WC_Product_Subscription extends WC_Product {

    /**
     * 商品タイプの識別子
     * product_type_selector に登録した値と一致させる
     */
    public string $product_type = 'subscription';

    /**
     * コンストラクタ
     */
    public function __construct( $product ) {
        // 親クラスのコンストラクタを呼び出す
        parent::__construct( $product );
    }

    /**
     * 商品タイプを返す
     */
    public function get_type(): string {
        return 'subscription';
    }

    /**
     * カスタムデータ:サブスクリプション期間(月数)を取得する
     */
    public function get_subscription_period(): int {
        return (int) $this->get_meta( '_subscription_period', true );
    }

    /**
     * カスタムデータ:サブスクリプション期間を設定する
     */
    public function set_subscription_period( int $period ): void {
        $this->update_meta_data( '_subscription_period', $period );
    }

    /**
     * カスタムデータ:更新サイクル(月次/年次)を取得する
     */
    public function get_billing_cycle(): string {
        return (string) $this->get_meta( '_billing_cycle', true ) ?: 'monthly';
    }

    /**
     * カスタムデータ:更新サイクルを設定する
     */
    public function set_billing_cycle( string $cycle ): void {
        $allowed = [ 'monthly', 'yearly', 'quarterly' ];
        if ( in_array( $cycle, $allowed, true ) ) {
            $this->update_meta_data( '_billing_cycle', $cycle );
        }
    }

    /**
     * カート追加可能かどうかの判定
     */
    public function is_purchasable(): bool {
        // 有効なサブスクリプション期間が設定されていれば購入可能
        return parent::is_purchasable()
            && $this->get_subscription_period() > 0;
    }
}

ステップ3:商品タイプのドロップダウンに JavaScript でパネルを表示する

<?php
// functions.php またはプラグインファイルに追記

// カスタム商品タイプ選択時に商品データパネルを制御する
add_action( 'admin_footer', 'subscription_product_type_options_js' );

function subscription_product_type_options_js() {
    global $post;
    if ( ! $post || 'product' !== $post->post_type ) {
        return;
    }
    ?>
    <script type="text/javascript">
    (function($) {
        // 商品タイプのドロップダウン変更時にパネルを制御する
        $('#product-type').on('change', function() {
            var productType = $(this).val();

            if ('subscription' === productType) {
                // 通常の価格フィールドを表示(継承するため)
                $('.product_data_tabs .general_options').show();
                // バリエーションタブは非表示
                $('.product_data_tabs .variations_options').hide();
                // カスタムパネルを表示
                $('.subscription_product_options').show();
            } else {
                $('.subscription_product_options').hide();
            }
        }).trigger('change');
    })(jQuery);
    </script>
    <?php
}

ステップ4:管理画面に独自メタフィールドを追加する

<?php
// 商品編集画面の「一般」タブにカスタムフィールドを追加する
add_action(
    'woocommerce_product_options_general_product_data',
    'add_subscription_product_fields'
);

function add_subscription_product_fields(): void {
    global $post;
    $product = wc_get_product( $post->ID );

    // WC_Product_Subscription 型の商品でのみ表示
    echo '<div class="subscription_product_options" style="display:none;">';
    echo '<h4>' . esc_html__( 'サブスクリプション設定', 'my-plugin' ) . '</h4>';

    // サブスクリプション期間の入力フィールド
    woocommerce_wp_text_input( [
        'id'          => '_subscription_period',
        'label'       => __( 'サブスクリプション期間(月数)', 'my-plugin' ),
        'placeholder' => '12',
        'desc_tip'    => true,
        'description' => __( '契約期間を月数で入力してください。', 'my-plugin' ),
        'type'        => 'number',
        'custom_attributes' => [
            'min'  => '1',
            'step' => '1',
        ],
        'value' => $product instanceof WC_Product_Subscription
            ? $product->get_subscription_period()
            : '',
    ] );

    // 更新サイクルの選択フィールド
    woocommerce_wp_select( [
        'id'      => '_billing_cycle',
        'label'   => __( '請求サイクル', 'my-plugin' ),
        'options' => [
            'monthly'   => __( '月次', 'my-plugin' ),
            'quarterly' => __( '四半期', 'my-plugin' ),
            'yearly'    => __( '年次', 'my-plugin' ),
        ],
        'value' => $product instanceof WC_Product_Subscription
            ? $product->get_billing_cycle()
            : 'monthly',
    ] );

    echo '</div>';
}

ステップ5:カスタム商品データを保存する

<?php
// woocommerce_process_product_meta アクションでデータを保存する
add_action(
    'woocommerce_process_product_meta',
    'save_subscription_product_fields',
    10,
    1
);

function save_subscription_product_fields( int $post_id ): void {
    $product = wc_get_product( $post_id );

    // カスタム商品タイプの場合のみ処理する
    if ( ! $product instanceof WC_Product_Subscription ) {
        return;
    }

    // サブスクリプション期間を保存する(バリデーション付き)
    if ( isset( $_POST['_subscription_period'] ) ) {
        $period = absint( $_POST['_subscription_period'] );
        if ( $period > 0 ) {
            $product->set_subscription_period( $period );
        }
    }

    // 請求サイクルを保存する
    if ( isset( $_POST['_billing_cycle'] ) ) {
        $allowed_cycles = [ 'monthly', 'quarterly', 'yearly' ];
        $cycle = sanitize_text_field( wp_unslash( $_POST['_billing_cycle'] ) );
        if ( in_array( $cycle, $allowed_cycles, true ) ) {
            $product->set_billing_cycle( $cycle );
        }
    }

    // 変更を保存する
    $product->save();
}

// カート・注文に商品メタデータを引き継ぐ
add_filter(
    'woocommerce_add_cart_item_data',
    'add_subscription_cart_item_data',
    10,
    2
);

function add_subscription_cart_item_data( array $cart_item_data, int $product_id ): array {
    $product = wc_get_product( $product_id );

    if ( $product instanceof WC_Product_Subscription ) {
        // カートアイテムにサブスクリプション情報を付加
        $cart_item_data['subscription_period'] = $product->get_subscription_period();
        $cart_item_data['billing_cycle']       = $product->get_billing_cycle();
    }

    return $cart_item_data;
}

注意事項

  • WooCommerce のバージョン確認: WC_Product クラスの API はバージョンによって変更されることがあります。woocommerce_product_class フィルターは WooCommerce 3.0 以降で利用可能です。
  • HPOS(High Performance Order Storage)対応: WooCommerce 7.1 以降では HPOS が導入されています。カスタムクラスが HPOS に対応しているか確認してください。
  • テスト: カスタム商品タイプはカート・チェックアウト・注文管理の各フローで十分にテストしてください。

まとめ

WooCommerce のカスタム商品タイプは「フィルター登録→クラス継承→管理画面への追加→データ保存」の4ステップで実装できます。WC_Product を適切に継承することで、WooCommerce のエコシステムと完全に互換性を保ちながら独自のビジネスロジックを実現できます。関連記事:WooCommerceのカスタム決済ゲートウェイを実装する方法

お気軽にご相談ください

お見積りへ お問い合わせへ