By Devport Team | Last updated: 2025-07-12 | 14 min read

Creating Custom Product Types in WooCommerce

WooCommerce's extensible product system allows you to create custom product types that go beyond simple, variable, and grouped products. This guide explores how to develop custom product types for unique business requirements, from subscription boxes to customizable products. Learn the complete development process, including data handling, pricing logic, and admin interface customization.

Whether you're building rental products, bookable services, or complex configurators, custom product types provide the flexibility to model any business logic. We'll cover the technical implementation details and best practices that ensure your custom products integrate seamlessly with WooCommerce.

Table of Contents

  1. Understanding Product Type Architecture
  2. Creating Your First Custom Product Type
  3. Advanced Product Features
  4. Admin Interface Customization
  5. Frontend Display and Cart Handling
  6. Real-World Examples

Understanding Product Type Architecture

WooCommerce Product Class Hierarchy

// Understanding the product class structure
abstract class WC_Product {
    protected $id = 0;
    protected $data = [
        'name' => '',
        'slug' => '',
        'status' => 'publish',
        'featured' => false,
        'catalog_visibility' => 'visible',
        'description' => '',
        'short_description' => '',
        'sku' => '',
        'price' => '',
        'regular_price' => '',
        'sale_price' => '',
        'tax_status' => 'taxable',
        'tax_class' => '',
        'manage_stock' => false,
        'stock_quantity' => null,
        'stock_status' => 'instock',
        'backorders' => 'no',
        'low_stock_amount' => '',
        'sold_individually' => false,
        'weight' => '',
        'length' => '',
        'width' => '',
        'height' => '',
        'parent_id' => 0,
        'reviews_allowed' => true,
        'purchase_note' => '',
        'attributes' => [],
        'default_attributes' => [],
        'menu_order' => 0,
        'virtual' => false,
        'downloadable' => false,
    ];

    /**
     * Get product type
     */
    public function get_type() {
        return 'simple';
    }

    /**
     * Product type specific features
     */
    public function supports($feature) {
        return in_array($feature, $this->get_supports());
    }

    /**
     * Get supported features
     */
    protected function get_supports() {
        return [];
    }
}

// Custom product type base class
abstract class WC_Product_Custom_Base extends WC_Product {

    /**
     * Custom data fields
     */
    protected function get_custom_data() {
        return [
            'custom_field_1' => '',
            'custom_field_2' => '',
            'pricing_type' => 'fixed',
            'booking_duration' => 0,
            'rental_period' => 'day',
        ];
    }

    /**
     * Initialize custom product
     */
    public function __construct($product = 0) {
        // Merge custom data
        $this->data = array_merge($this->data, $this->get_custom_data());

        // Set default supports
        parent::__construct($product);
    }

    /**
     * Custom validation
     */
    public function validate_props() {
        parent::validate_props();

        // Add custom validation logic
        if ($this->get_pricing_type() === 'dynamic' && !$this->get_price_calculator()) {
            $this->error('dynamic_pricing_requires_calculator', 
                __('Dynamic pricing requires a price calculator', 'woocommerce'));
        }
    }
}

Creating Your First Custom Product Type

Rental Product Type Example

// Complete rental product implementation
class WC_Product_Rental extends WC_Product {

    /**
     * Product type name
     */
    protected $product_type = 'rental';

    /**
     * Get product type
     */
    public function get_type() {
        return 'rental';
    }

    /**
     * Get supported features
     */
    protected function get_supports() {
        return [
            'ajax_add_to_cart',
            'shipping',
            'stock',
        ];
    }

    /**
     * Get rental price per period
     */
    public function get_rental_price($period = 1) {
        $base_price = $this->get_price();
        $price_per_period = $this->get_meta('_rental_price_per_period');

        if ($price_per_period) {
            return $price_per_period * $period;
        }

        return $base_price * $period;
    }

    /**
     * Get rental periods
     */
    public function get_rental_periods() {
        return [
            'hour' => __('Hour', 'woocommerce'),
            'day' => __('Day', 'woocommerce'),
            'week' => __('Week', 'woocommerce'),
            'month' => __('Month', 'woocommerce'),
        ];
    }

    /**
     * Get minimum rental period
     */
    public function get_minimum_rental_period() {
        return $this->get_meta('_minimum_rental_period') ?: 1;
    }

    /**
     * Get maximum rental period
     */
    public function get_maximum_rental_period() {
        return $this->get_meta('_maximum_rental_period') ?: 365;
    }

    /**
     * Check availability for date range
     */
    public function is_available_for_dates($start_date, $end_date) {
        global $wpdb;

        // Check existing bookings
        $bookings = $wpdb->get_results($wpdb->prepare("
            SELECT * FROM {$wpdb->prefix}wc_rental_bookings
            WHERE product_id = %d
            AND status IN ('confirmed', 'pending')
            AND (
                (start_date <= %s AND end_date >= %s)
                OR (start_date <= %s AND end_date >= %s)
                OR (start_date >= %s AND end_date <= %s)
            )
        ", $this->get_id(), $start_date, $start_date, $end_date, $end_date, $start_date, $end_date));

        if (!empty($bookings)) {
            // Check if stock allows multiple rentals
            $stock = $this->get_stock_quantity();
            if ($stock !== null && count($bookings) >= $stock) {
                return false;
            }
        }

        return true;
    }

    /**
     * Add to cart validation
     */
    public function validate_add_to_cart($passed, $product_id, $quantity, $variation_id = 0, $variations = []) {
        if (!$passed) {
            return false;
        }

        // Check if rental dates are provided
        if (empty($_REQUEST['rental_start_date']) || empty($_REQUEST['rental_end_date'])) {
            wc_add_notice(__('Please select rental dates', 'woocommerce'), 'error');
            return false;
        }

        $start_date = sanitize_text_field($_REQUEST['rental_start_date']);
        $end_date = sanitize_text_field($_REQUEST['rental_end_date']);

        // Validate dates
        if (strtotime($start_date) >= strtotime($end_date)) {
            wc_add_notice(__('End date must be after start date', 'woocommerce'), 'error');
            return false;
        }

        // Check availability
        if (!$this->is_available_for_dates($start_date, $end_date)) {
            wc_add_notice(__('Product is not available for selected dates', 'woocommerce'), 'error');
            return false;
        }

        return true;
    }
}

// Register rental product type
class WC_Product_Rental_Init {

    public function __construct() {
        // Register product type
        add_filter('product_type_selector', [$this, 'add_product_type']);
        add_filter('woocommerce_product_class', [$this, 'product_class'], 10, 2);

        // Admin hooks
        add_action('woocommerce_product_options_general_product_data', [$this, 'add_rental_fields']);
        add_action('woocommerce_process_product_meta_rental', [$this, 'save_rental_fields']);

        // Frontend hooks
        add_action('woocommerce_rental_add_to_cart', [$this, 'add_to_cart_template']);
        add_filter('woocommerce_add_cart_item_data', [$this, 'add_cart_item_data'], 10, 3);
        add_filter('woocommerce_cart_item_price', [$this, 'cart_item_price'], 10, 3);
    }

    /**
     * Add rental product type
     */
    public function add_product_type($types) {
        $types['rental'] = __('Rental product', 'woocommerce');
        return $types;
    }

    /**
     * Filter product class
     */
    public function product_class($classname, $product_type) {
        if ($product_type === 'rental') {
            return 'WC_Product_Rental';
        }
        return $classname;
    }

    /**
     * Add rental fields to admin
     */
    public function add_rental_fields() {
        global $post;

        echo '<div class="options_group show_if_rental clear">';

        // Rental price per period
        woocommerce_wp_text_input([
            'id' => '_rental_price_per_period',
            'label' => __('Rental price per period', 'woocommerce'),
            'data_type' => 'price',
        ]);

        // Rental period
        woocommerce_wp_select([
            'id' => '_rental_period',
            'label' => __('Rental period', 'woocommerce'),
            'options' => [
                'hour' => __('Hour', 'woocommerce'),
                'day' => __('Day', 'woocommerce'),
                'week' => __('Week', 'woocommerce'),
                'month' => __('Month', 'woocommerce'),
            ],
        ]);

        // Minimum rental period
        woocommerce_wp_text_input([
            'id' => '_minimum_rental_period',
            'label' => __('Minimum rental period', 'woocommerce'),
            'type' => 'number',
            'custom_attributes' => [
                'min' => '1',
                'step' => '1',
            ],
        ]);

        // Maximum rental period
        woocommerce_wp_text_input([
            'id' => '_maximum_rental_period',
            'label' => __('Maximum rental period', 'woocommerce'),
            'type' => 'number',
            'custom_attributes' => [
                'min' => '1',
                'step' => '1',
            ],
        ]);

        echo '</div>';
    }
}

// Initialize rental product type
new WC_Product_Rental_Init();

Advanced Product Features

Dynamic Pricing and Calculations

// Advanced pricing calculator
class WC_Product_Custom_Calculator extends WC_Product {

    protected $product_type = 'custom_calculator';

    /**
     * Calculate dynamic price
     */
    public function calculate_price($args = []) {
        $base_price = $this->get_base_price();
        $total = $base_price;

        // Get pricing rules
        $rules = $this->get_pricing_rules();

        foreach ($rules as $rule) {
            switch ($rule['type']) {
                case 'multiply':
                    $total *= $this->evaluate_rule_value($rule, $args);
                    break;

                case 'add':
                    $total += $this->evaluate_rule_value($rule, $args);
                    break;

                case 'percentage':
                    $total += ($total * $this->evaluate_rule_value($rule, $args) / 100);
                    break;

                case 'tiered':
                    $total = $this->calculate_tiered_price($rule, $args, $total);
                    break;
            }
        }

        return apply_filters('woocommerce_product_calculated_price', $total, $this, $args);
    }

    /**
     * Get pricing rules
     */
    public function get_pricing_rules() {
        $rules = $this->get_meta('_pricing_rules');

        if (!is_array($rules)) {
            $rules = [];
        }

        return $rules;
    }

    /**
     * Calculate tiered pricing
     */
    private function calculate_tiered_price($rule, $args, $base_price) {
        $quantity = isset($args['quantity']) ? intval($args['quantity']) : 1;
        $tiers = $rule['tiers'];

        usort($tiers, function($a, $b) {
            return $a['min_quantity'] - $b['min_quantity'];
        });

        foreach ($tiers as $tier) {
            if ($quantity >= $tier['min_quantity']) {
                if ($tier['price_type'] === 'fixed') {
                    return $tier['price'];
                } elseif ($tier['price_type'] === 'percentage') {
                    return $base_price * (1 - $tier['discount'] / 100);
                }
            }
        }

        return $base_price;
    }

    /**
     * AJAX price calculation endpoint
     */
    public static function ajax_calculate_price() {
        check_ajax_referer('wc_custom_calculator_nonce', 'nonce');

        $product_id = intval($_POST['product_id']);
        $product = wc_get_product($product_id);

        if (!$product || !is_a($product, 'WC_Product_Custom_Calculator')) {
            wp_send_json_error('Invalid product');
        }

        $args = $_POST['calculation_args'] ?? [];
        $price = $product->calculate_price($args);

        wp_send_json_success([
            'price' => $price,
            'formatted_price' => wc_price($price),
        ]);
    }
}

// Register AJAX handler
add_action('wp_ajax_calculate_custom_price', ['WC_Product_Custom_Calculator', 'ajax_calculate_price']);
add_action('wp_ajax_nopriv_calculate_custom_price', ['WC_Product_Custom_Calculator', 'ajax_calculate_price']);

Composite Product Type

// Composite product with components
class WC_Product_Composite extends WC_Product {

    protected $product_type = 'composite';

    /**
     * Get product components
     */
    public function get_components() {
        $components = $this->get_meta('_components');

        if (!is_array($components)) {
            return [];
        }

        // Enhance component data
        foreach ($components as &$component) {
            $component['products'] = $this->get_component_products($component);
            $component['default_product'] = $this->get_component_default($component);
        }

        return $components;
    }

    /**
     * Get component products
     */
    private function get_component_products($component) {
        $products = [];

        if (!empty($component['product_ids'])) {
            foreach ($component['product_ids'] as $product_id) {
                $product = wc_get_product($product_id);
                if ($product) {
                    $products[] = [
                        'id' => $product->get_id(),
                        'name' => $product->get_name(),
                        'price' => $product->get_price(),
                        'image' => $product->get_image(),
                    ];
                }
            }
        } elseif (!empty($component['category_ids'])) {
            $products = wc_get_products([
                'category' => $component['category_ids'],
                'limit' => -1,
                'return' => 'ids',
            ]);
        }

        return $products;
    }

    /**
     * Calculate composite price
     */
    public function get_composite_price($configuration = []) {
        $price = 0;
        $components = $this->get_components();

        foreach ($components as $component_id => $component) {
            if (isset($configuration[$component_id])) {
                $product_id = $configuration[$component_id]['product_id'];
                $quantity = $configuration[$component_id]['quantity'] ?? 1;

                $product = wc_get_product($product_id);
                if ($product) {
                    $component_price = $product->get_price() * $quantity;

                    // Apply component discount
                    if (!empty($component['discount'])) {
                        $component_price *= (1 - $component['discount'] / 100);
                    }

                    $price += $component_price;
                }
            } elseif ($component['required']) {
                // Add default product price for required components
                $default_product = wc_get_product($component['default_product']);
                if ($default_product) {
                    $price += $default_product->get_price();
                }
            }
        }

        return $price;
    }
}

Admin Interface Customization

Custom Product Data Panels

// Advanced admin interface for custom products
class WC_Custom_Product_Admin {

    public function __construct() {
        // Add custom product data tabs
        add_filter('woocommerce_product_data_tabs', [$this, 'add_product_data_tabs']);
        add_action('woocommerce_product_data_panels', [$this, 'add_product_data_panels']);

        // Save custom fields
        add_action('woocommerce_process_product_meta', [$this, 'save_custom_fields']);

        // Admin scripts
        add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_scripts']);
    }

    /**
     * Add custom product data tabs
     */
    public function add_product_data_tabs($tabs) {
        $tabs['custom_options'] = [
            'label' => __('Custom Options', 'woocommerce'),
            'target' => 'custom_options_data',
            'class' => ['show_if_custom_type'],
            'priority' => 21,
        ];

        $tabs['pricing_rules'] = [
            'label' => __('Pricing Rules', 'woocommerce'),
            'target' => 'pricing_rules_data',
            'class' => ['show_if_custom_calculator'],
            'priority' => 22,
        ];

        return $tabs;
    }

    /**
     * Add product data panels
     */
    public function add_product_data_panels() {
        ?>
        <div id="custom_options_data" class="panel woocommerce_options_panel">
            <div class="options_group">
                <?php
                // Custom fields
                woocommerce_wp_text_input([
                    'id' => '_custom_field_1',
                    'label' => __('Custom Field 1', 'woocommerce'),
                    'desc_tip' => true,
                    'description' => __('Enter custom field value', 'woocommerce'),
                ]);

                woocommerce_wp_select([
                    'id' => '_custom_option',
                    'label' => __('Custom Option', 'woocommerce'),
                    'options' => [
                        'option1' => __('Option 1', 'woocommerce'),
                        'option2' => __('Option 2', 'woocommerce'),
                        'option3' => __('Option 3', 'woocommerce'),
                    ],
                ]);
                ?>
            </div>

            <div class="options_group">
                <p class="form-field">
                    <label><?php esc_html_e('Components', 'woocommerce'); ?></label>
                    <div id="custom_components_container">
                        <!-- Dynamic components will be added here -->
                    </div>
                    <button type="button" class="button add_component">
                        <?php esc_html_e('Add Component', 'woocommerce'); ?>
                    </button>
                </p>
            </div>
        </div>

        <div id="pricing_rules_data" class="panel woocommerce_options_panel">
            <div class="options_group">
                <div id="pricing_rules_container">
                    <!-- Pricing rules interface -->
                </div>
            </div>
        </div>
        <?php
    }

    /**
     * Enqueue admin scripts
     */
    public function enqueue_admin_scripts($hook) {
        if ('post.php' !== $hook && 'post-new.php' !== $hook) {
            return;
        }

        global $post;
        if ('product' !== $post->post_type) {
            return;
        }

        wp_enqueue_script(
            'wc-custom-product-admin',
            plugins_url('assets/js/admin.js', __FILE__),
            ['jquery', 'jquery-ui-sortable'],
            '1.0.0',
            true
        );

        wp_localize_script('wc-custom-product-admin', 'wcCustomProduct', [
            'nonce' => wp_create_nonce('wc_custom_product'),
            'i18n' => [
                'addComponent' => __('Add Component', 'woocommerce'),
                'removeComponent' => __('Remove', 'woocommerce'),
                'confirmDelete' => __('Are you sure?', 'woocommerce'),
            ],
        ]);
    }
}

new WC_Custom_Product_Admin();

Frontend Display and Cart Handling

Custom Add to Cart Template

// Frontend display for custom products
class WC_Custom_Product_Frontend {

    public function __construct() {
        // Template hooks
        add_action('woocommerce_custom_calculator_add_to_cart', [$this, 'calculator_add_to_cart']);
        add_action('woocommerce_composite_add_to_cart', [$this, 'composite_add_to_cart']);

        // Cart handling
        add_filter('woocommerce_add_cart_item_data', [$this, 'add_cart_item_data'], 10, 3);
        add_filter('woocommerce_get_cart_item_from_session', [$this, 'get_cart_item_from_session'], 10, 3);
        add_filter('woocommerce_cart_item_name', [$this, 'cart_item_name'], 10, 3);
    }

    /**
     * Calculator product add to cart form
     */
    public function calculator_add_to_cart() {
        global $product;

        wc_get_template('single-product/add-to-cart/calculator.php', [
            'product' => $product,
            'pricing_rules' => $product->get_pricing_rules(),
        ]);
    }

    /**
     * Add custom data to cart item
     */
    public function add_cart_item_data($cart_item_data, $product_id, $variation_id) {
        $product = wc_get_product($product_id);

        if ($product->is_type('custom_calculator')) {
            // Store calculation parameters
            if (isset($_POST['calculation_args'])) {
                $cart_item_data['calculation_args'] = $_POST['calculation_args'];
                $cart_item_data['calculated_price'] = $product->calculate_price($_POST['calculation_args']);
            }
        } elseif ($product->is_type('composite')) {
            // Store component selection
            if (isset($_POST['composite_configuration'])) {
                $cart_item_data['composite_configuration'] = $_POST['composite_configuration'];
                $cart_item_data['composite_price'] = $product->get_composite_price($_POST['composite_configuration']);
            }
        }

        return $cart_item_data;
    }

    /**
     * Display custom cart item data
     */
    public function cart_item_name($name, $cart_item, $cart_item_key) {
        if (isset($cart_item['calculation_args'])) {
            $name .= '<dl class="variation">';
            foreach ($cart_item['calculation_args'] as $key => $value) {
                $name .= '<dt>' . esc_html($key) . ':</dt>';
                $name .= '<dd>' . esc_html($value) . '</dd>';
            }
            $name .= '</dl>';
        }

        if (isset($cart_item['composite_configuration'])) {
            $name .= '<ul class="composite-components">';
            foreach ($cart_item['composite_configuration'] as $component_id => $config) {
                $component_product = wc_get_product($config['product_id']);
                if ($component_product) {
                    $name .= '<li>' . esc_html($component_product->get_name());
                    if ($config['quantity'] > 1) {
                        $name .= ' &times; ' . $config['quantity'];
                    }
                    $name .= '</li>';
                }
            }
            $name .= '</ul>';
        }

        return $name;
    }
}

new WC_Custom_Product_Frontend();

Real-World Examples

Subscription Box Product

// Subscription box product type
class WC_Product_Subscription_Box extends WC_Product {

    protected $product_type = 'subscription_box';

    /**
     * Get subscription intervals
     */
    public function get_subscription_intervals() {
        return [
            'weekly' => __('Weekly', 'woocommerce'),
            'monthly' => __('Monthly', 'woocommerce'),
            'quarterly' => __('Quarterly', 'woocommerce'),
        ];
    }

    /**
     * Get box contents for period
     */
    public function get_box_contents($period_number) {
        $all_contents = $this->get_meta('_box_contents');

        if (isset($all_contents[$period_number])) {
            return $all_contents[$period_number];
        }

        // Return rotating contents
        $rotation_size = count($all_contents);
        if ($rotation_size > 0) {
            $index = $period_number % $rotation_size;
            return $all_contents[$index];
        }

        return [];
    }

    /**
     * Calculate next box date
     */
    public function get_next_box_date($last_date = null) {
        if (!$last_date) {
            $last_date = current_time('Y-m-d');
        }

        $interval = $this->get_subscription_interval();

        switch ($interval) {
            case 'weekly':
                return date('Y-m-d', strtotime($last_date . ' +1 week'));
            case 'monthly':
                return date('Y-m-d', strtotime($last_date . ' +1 month'));
            case 'quarterly':
                return date('Y-m-d', strtotime($last_date . ' +3 months'));
        }

        return $last_date;
    }
}

// Initialize subscription box type
add_action('init', function() {
    if (class_exists('WC_Product_Subscription_Box')) {
        // Register product type
        add_filter('product_type_selector', function($types) {
            $types['subscription_box'] = __('Subscription Box', 'woocommerce');
            return $types;
        });

        add_filter('woocommerce_product_class', function($classname, $product_type) {
            if ($product_type === 'subscription_box') {
                return 'WC_Product_Subscription_Box';
            }
            return $classname;
        }, 10, 2);
    }
});

Creating custom product types in WooCommerce opens up endless possibilities for unique e-commerce experiences. By understanding the product architecture and implementing proper admin interfaces, cart handling, and display logic, you can build product types that perfectly match your business requirements.