Skip to content
ThemesIonic — home
WooCommerce

Placing Content Inside the WooCommerce Product Summary

Everything in the summary column — title, price, add-to-cart form, meta — is a callback on this one hook, each at a fixed priority. Your output lands wherever its priority says, and the add-to-cart form sits at 30.

Updated 4 min read intermediate

The summary column is not a template you edit; it is a stack of callbacks on one hook. woocommerce_single_product_summary fires once inside the summary div of the single product page, and WooCommerce attaches the title, rating, price, short description, add-to-cart form and meta to it at fixed priorities. To put something between the price and the button you do not touch a file. You pick a number.

The priorities already in use

Priority Callback Output
5 woocommerce_template_single_title Product title
10 woocommerce_template_single_rating Star rating
10 woocommerce_template_single_price Price
20 woocommerce_template_single_excerpt Short description
30 woocommerce_template_single_add_to_cart Add-to-cart form
40 woocommerce_template_single_meta SKU, categories, tags
50 woocommerce_template_single_sharing Sharing placeholder
60 WC_Structured_Data::generate_product_data JSON-LD

Two callbacks share priority 10, and between callbacks at the same priority the order is the order they were added. Adding your own at 10 puts it after both. Everything else follows from the table: 6 lands under the title, 15 between price and short description, 25 directly above the button, 35 directly below it.

add_action( 'woocommerce_single_product_summary', 'ti_delivery_estimate', 25 );

function ti_delivery_estimate() {
	global $product;

	if ( ! $product instanceof WC_Product || ! $product->is_in_stock() ) {
		return;
	}

	echo '<p class="ti-delivery">' . esc_html__( 'Dispatched within two working days.', 'ti' ) . '</p>';
}

The global $product line is the one people forget. The variable is set because the hook runs inside the loop, but a function cannot see it without declaring it.

Conditions that belong in the callback

The hook fires on every single product page, so the callback decides which products get the output. The product object carries what you need:

  • $product->get_type() returns simple, variable, grouped or external. A delivery note under a grouped product's list of children reads as nonsense.
  • $product->is_in_stock() and $product->is_purchasable() separate buyable products from catalogue-only ones.
  • $product->get_category_ids() or has_term( 'furniture', 'product_cat', $product->get_id() ) scopes by category.

For a variable product, remember that price and stock belong to the chosen variation and update in the browser after the page is served. Server-side output based on $product->is_in_stock() reflects the parent, which is in stock if any variation is. If the message must track the selected variation, it has to react to the found_variation JavaScript event, not to this hook. The same split governs product variations generally.

Removing or moving what is there

remove_action needs the hook, the callback and the priority — all three, exactly:

add_action( 'init', 'ti_move_short_description' );

function ti_move_short_description() {
	remove_action( 'woocommerce_single_product_summary', 'woocommerce_template_single_excerpt', 20 );
	add_action( 'woocommerce_single_product_summary', 'woocommerce_template_single_excerpt', 35 );
}

That moves the short description below the add-to-cart form. WooCommerce registers its callbacks when the plugin loads, before any theme code runs, so a call straight from functions.php works; the init wrapper is for snippets living in a plugin that may load before WooCommerce. Calling remove_action with the wrong priority — or too early — is the quiet failure: nothing errors, nothing changes.

Removing the meta block at 40 is a frequent request and a mild mistake. The SKU it prints is the same SKU shoppers paste into support tickets, and hiding the whole block for the sake of the category links loses it. Hide the pieces with CSS or filter wc_product_sku_enabled instead.

Where the hook does not fire

  • Block-based single product templates. The Single Product template WooCommerce ships for block themes is the Classic Template block, which renders the legacy PHP template, so on an unmodified block theme the hook still fires. Once the template has been converted to blocks — Product Title, Product Price, Add to Cart — each block prints its own markup and the hook is gone. On a converted template, work with product blocks rather than hooks.
  • Quick view modals. Quick view plugins usually render a trimmed version of the summary with their own hook names, so the output appears on the product page and not in the modal, or the reverse.
  • The shop grid. Nothing in the loop runs this hook. The grid uses woocommerce_after_shop_loop_item and woocommerce_after_shop_loop_item_title, which is where a product badge belongs.
  • A theme that replaced the template. A theme with its own content-single-product.php may call a different hook or none at all. Switch to a default theme for one request before blaming the callback.

Verify

Load a simple product and a variable product. Confirm the output is where the priority puts it on both, then pick a product that fails your condition and confirm it is absent. If nothing shows anywhere, check whether WooCommerce's own price is rendered through the hook: temporarily hook a callback at priority 1 that prints a marker. No marker means the template in use never calls the hook, and the fix is in the theme, not in your function. If the page is wrong in other ways too, start from product data tabs not working, which covers the companion hook below the summary.

Frequently asked

The add-to-cart form is hooked at priority 30. Anything you add at the default priority of 10 shares that slot with the rating and price and lands after them, but a callback at 31 or higher comes after the form. Use 25 to sit between the excerpt and the form.
The hook runs inside the loop, so global $product is set — but only if you declare it with the global keyword inside the function. A callback that calls get_the_ID() and wc_get_product() instead is also safe and works in contexts where the global was never populated.
On the default template, yes: WooCommerce ships the block-theme Single Product template as a Classic Template block, which renders the legacy PHP template and all of its hooks. It stops firing once someone converts that template to individual product blocks, because each block prints its own markup.

Related guides