Add custom product tabs to WooCommerce with PHP

WooCommerce renders three default tabs on every single product page: Description (the main post content), Additional Information (attributes and dimensions), and Reviews. For most stores, these are not enough — you may need a Shipping & Returns tab, a Video tab, a Downloads tab for digital products, or a custom tab whose content comes from a custom field. WooCommerce exposes the complete tab system through a single filter: woocommerce_product_tabs. Each tab is an array entry with a title, a priority (controls tab order), and a callback (the function that renders the tab content panel). Adding a tab is as simple as appending a new entry to the array. Removing a default tab is done with unset() on the known keys (description, additional_information, reviews). Modifying an existing tab’s title or priority is done by directly overwriting the array entry. This is far cleaner than JavaScript-based tab injection and works correctly with WooCommerce’s built-in tab accessibility markup (ARIA roles and keyboard navigation). Tab content is output inside a <div id="tab-{key}"> panel that WooCommerce styles and makes accessible automatically. Combine custom tabs with the product gallery guide and price display guide for a fully customised product page.

Problem: The default WooCommerce product tabs (Description, Additional Information, Reviews) do not match your store’s needs and you want to add, remove, or rename them without a plugin.

Solution: Add the following code to your theme’s functions.php file:

add_filter( 'woocommerce_product_tabs', 'helloadmin_custom_product_tabs' );
function helloadmin_custom_product_tabs( array $tabs ): array {

    // Remove the Reviews tab
    unset( $tabs['reviews'] );

    // Rename the Description tab
    if ( isset( $tabs['description'] ) ) {
        $tabs['description']['title'] = __( 'Product Details', 'woocommerce' );
    }

    // Move Additional Information to appear last (priority 50 → 30)
    if ( isset( $tabs['additional_information'] ) ) {
        $tabs['additional_information']['priority'] = 30;
    }

    // Add a Shipping & Returns tab
    $tabs['shipping_returns'] = [
        'title'    => __( 'Shipping & Returns', 'woocommerce' ),
        'priority' => 40,
        'callback' => 'helloadmin_shipping_returns_tab',
    ];

    // Add a custom tab from a product custom field
    global $product;
    $video_url = $product ? get_post_meta( $product->get_id(), '_helloadmin_video_url', true ) : '';
    if ( $video_url ) {
        $tabs['video'] = [
            'title'    => __( 'Video', 'woocommerce' ),
            'priority' => 20,
            'callback' => 'helloadmin_video_tab',
        ];
    }

    return $tabs;
}

// Tab content callbacks
function helloadmin_shipping_returns_tab(): void {
    // You can also use get_option() to pull from a settings page
    echo '<h2>' . esc_html__( 'Shipping & Returns', 'woocommerce' ) . '</h2>';
    echo '<p>' . esc_html__( 'We ship worldwide. Orders are dispatched within 2 business days. Returns accepted within 30 days of delivery.', 'woocommerce' ) . '</p>';
}

function helloadmin_video_tab(): void {
    global $product;
    if ( ! $product ) {
        return;
    }
    $video_url = esc_url( get_post_meta( $product->get_id(), '_helloadmin_video_url', true ) );
    if ( ! $video_url ) {
        return;
    }
    echo '<h2>' . esc_html__( 'Product Video', 'woocommerce' ) . '</h2>';
    printf(
        '<div class="product-video-wrapper">
            <iframe width="560" height="315" src="%s" allowfullscreen loading="lazy"></iframe>
        </div>',
        $video_url
    );
}

NOTE: Tab priorities determine display order — lower numbers appear first. The default priorities are: Description = 10, Additional Information = 20, Reviews = 30. Removing the Reviews tab via unset( $tabs[‘reviews’] ) only removes the tab on the front end; WooCommerce still allows customers to submit reviews via the admin. If you want to disable reviews completely, also go to WooCommerce → Settings → Products and uncheck “Enable product reviews”. Custom tab content callbacks should always escape output with esc_html() or wp_kses_post() — never echo raw post meta values directly.