Building Composite Products in WooCommerce Without a Plugin

A “composite product” bundles multiple component products that the customer can choose individually — for example, a PC builder where the customer picks CPU, RAM, and storage from defined option lists. Without the commercial plugin, you can build this with a variable product parent, custom meta, and a custom cart item handler.

Problem: WooCommerce does not natively support selling a product as a bundle of individually selectable components — each with its own quantity and optional add-on — without a paid extension.

Solution: Build composite products using a custom product type that extends WC_Product — register component meta fields on the product edit screen, add component selection to the single-product template, validate component selections on add-to-cart, and carry component data through the cart into the order line item meta.

The examples below create a composite product using standard WooCommerce hooks: a parent product holds component definitions in meta, a custom template renders the component selectors, and a validation hook ensures all components are chosen before adding to cart.

<?php
// 1. Store component definitions in parent product meta
// Each component: [ 'label' => 'Choose CPU', 'product_ids' => [101, 102, 103], 'required' => true ]
function myplugin_save_composite_components( int $post_id ): void {
    if ( 'product' !== get_post_type( $post_id ) ) return;
    $raw = wp_unslash( $_POST['composite_components'] ?? '' );
    $components = json_decode( $raw, true );
    if ( is_array( $components ) ) {
        update_post_meta( $post_id, '_composite_components', $components );
    }
}
add_action( 'save_post', 'myplugin_save_composite_components' );

// 2. Render component selectors on the product page
add_action( 'woocommerce_before_add_to_cart_button', function() {
    global $product;
    $components = get_post_meta( $product->get_id(), '_composite_components', true );
    if ( ! is_array( $components ) ) return;

    foreach ( $components as $index => $component ) {
        $required = ! empty( $component['required'] ) ? 'required' : '';
        echo '<div class="composite-component">';
        echo '<label>' . esc_html( $component['label'] ) . '</label>';
        echo '<select name="composite_component[' . $index . ']" ' . $required . '>';
        echo '<option value="">— Select —</option>';
        foreach ( $component['product_ids'] as $pid ) {
            $p = wc_get_product( $pid );
            if ( $p ) {
                echo '<option value="' . esc_attr( $pid ) . '">' . esc_html( $p->get_name() )
                   . ' (+' . wc_price( $p->get_price() ) . ')</option>';
            }
        }
        echo '</select></div>';
    }
} );

Validate and add component child items to the cart:

<?php
// 3. Validate components before adding to cart
add_filter( 'woocommerce_add_to_cart_validation', function( bool $valid, int $product_id ): bool {
    $components = get_post_meta( $product_id, '_composite_components', true );
    if ( ! is_array( $components ) ) return $valid;

    $selected = array_map( 'absint', (array) ( $_POST['composite_component'] ?? [] ) );

    foreach ( $components as $index => $component ) {
        if ( ! empty( $component['required'] ) && empty( $selected[ $index ] ) ) {
            wc_add_notice( sprintf( __( 'Please select %s.', 'myplugin' ), $component['label'] ), 'error' );
            return false;
        }
    }
    return $valid;
}, 10, 2 );

// 4. Add parent + all selected child products to the cart together
add_action( 'woocommerce_add_to_cart', function( string $cart_key, int $product_id ) {
    $components = get_post_meta( $product_id, '_composite_components', true );
    if ( ! is_array( $components ) ) return;

    $selected = array_map( 'absint', (array) ( $_POST['composite_component'] ?? [] ) );
    foreach ( $selected as $child_id ) {
        if ( $child_id ) {
            WC()->cart->add_to_cart( $child_id, 1, 0, [], [ 'composite_parent' => $cart_key ] );
        }
    }
}, 10, 2 );

NOTE: This implementation covers the core flow. For production use, also handle cart item removal (remove children when parent is removed via woocommerce_remove_cart_item), price summation display, and order item grouping in the woocommerce_order_item_name filter.

Leave Comment

Your email address will not be published. Required fields are marked *