WooCommerce Hooks: 6 Examples for Product, Cart & Shop Pages

WooCommerce Hooks Product Page Guide

WooCommerce hooks let you change almost any part of a store without touching the plugin’s files. A client asks for “a line about delivery under the buy button” or “remove the Company field from checkout”, and the first thought is usually a plugin. Most of the time 5 to 10 lines of code are enough.

Below is what I use on real projects: how hooks work, the WooCommerce single product hooks with their priorities, the cart page and shop page hooks, the most useful filters, six working examples and a few mistakes that cost me time before.

Actions and filters: what’s the difference

A hook is a spot in the code where WordPress or WooCommerce lets you step in. There are two kinds.

An action means “do something here”, for example print a block of text. It doesn’t return anything.

A filter means “here’s a value, change it and give it back”, for example the button label or the list of form fields. A filter must return the value, otherwise it disappears.

Both work the same way: add_action() or add_filter() with the hook name, your function and a priority. A lower priority number runs earlier. The default is 10.

Where to put the code

Not in the main theme and not in the WooCommerce plugin, because an update will wipe it. Three normal options:

  • a child theme (functions.php);
  • your own small one-file plugin;
  • a snippets plugin like Code Snippets, if the code is for a single site and you don’t want to create files.

For client sites I make a separate plugin. The code doesn’t depend on the theme, so changing the theme won’t break the store.

WooCommerce single product hooks

The single product page is assembled from a few hooks. These are the ones I need most often, with what WooCommerce attaches to them by default:

HookPriorityWhat it outputs
woocommerce_before_single_product_summary10 / 20Sale badge / image gallery
woocommerce_single_product_summary5Title
woocommerce_single_product_summary10Rating, price
woocommerce_single_product_summary20Short description
woocommerce_single_product_summary30Add to cart button
woocommerce_single_product_summary40SKU, categories, tags
woocommerce_single_product_summary50Sharing hook (empty by default)
woocommerce_after_single_product_summary10 / 15 / 20Tabs / upsells / related products

Two more are handy: woocommerce_before_add_to_cart_button and woocommerce_after_add_to_cart_button. They sit right around the button.

Here is the same thing as a quick visual guide, in the order the page is rendered:

woocommerce_before_single_product
├─ woocommerce_before_single_product_summary
│    10 sale badge · 20 image gallery
├─ woocommerce_single_product_summary
│    5 title · 10 rating · 10 price · 20 short description
│    30 add to cart · 40 meta · 50 sharing
├─ woocommerce_after_single_product_summary
│    10 tabs · 15 upsells · 20 related products
└─ woocommerce_after_single_product

One detail people often miss. If you use a block theme and the product template is built from blocks (not the classic WooCommerce template), some of these hooks won’t fire, because blocks don’t call them. It’s easy to check: put a test echo on the hook and look at the page. If nothing shows up, either use the “Classic Template” block in the template, or change the output with the render_block filter.

Example 1. A delivery note under the button

Priority 35 sits between the button (30) and the meta (40), so the line appears right under the button.

add_action( 'woocommerce_single_product_summary', 'dd_delivery_note', 35 );

function dd_delivery_note() {
    echo '<p class="delivery-note">We ship the same day for orders placed before 3 PM.</p>';
}

If the text should differ per product, read it from a custom product field with get_post_meta() and check that it isn’t empty.

Example 2. Move a block somewhere else

By default the short description sits above the button. To show it below the meta, remove the default hook and add it again with a new priority.

add_action( 'init', 'dd_move_excerpt' );

function dd_move_excerpt() {
    remove_action( 'woocommerce_single_product_summary', 'woocommerce_template_single_excerpt', 20 );
    add_action( 'woocommerce_single_product_summary', 'woocommerce_template_single_excerpt', 45 );
}

Important: remove_action must use the same function name and the same priority as the original add_action. Otherwise nothing happens and you won’t see an error. That’s why I usually run it on init, when WooCommerce has already registered its hooks.

WooCommerce cart page hooks

The cart page has fewer hooks, but they cover everything you usually need: a message above the table, a note near the totals, something before the checkout button.

HookWhat it outputs
woocommerce_before_cartBefore the whole cart form
woocommerce_before_cart_tableAbove the product table
woocommerce_after_cart_tableBelow the product table
woocommerce_cart_collateralsCross-sells and the cart totals box
woocommerce_before_cart_totalsAbove the totals
woocommerce_proceed_to_checkoutThe checkout button (priority 20)
woocommerce_after_cartAfter the whole cart

Example 3. A note above the checkout button

add_action( 'woocommerce_proceed_to_checkout', 'dd_cart_note', 5 );

function dd_cart_note() {
    echo '<p class="cart-note">Free delivery on orders over $50.</p>';
}

The button itself is attached with priority 20, so priority 5 puts the note above it. This works with the classic cart (the shortcode). If your cart page uses the Cart block, these hooks don’t apply, so check which one you have first.

WooCommerce shop page hooks

The shop and category pages use a loop: the same set of hooks runs for every product card. Changes here affect all products in the list.

HookPriorityWhat it outputs
woocommerce_before_main_content10 / 20Wrapper start / breadcrumbs
woocommerce_archive_description10Category description
woocommerce_before_shop_loop20 / 30Result count / sorting dropdown
woocommerce_before_shop_loop_item10Opens the product link
woocommerce_shop_loop_item_title10Product title
woocommerce_after_shop_loop_item_title5 / 10Rating / price
woocommerce_after_shop_loop_item5 / 10Closes the link / Add to cart button
woocommerce_after_shop_loop10Pagination

Example 4. A stock note under the price in the product list

add_action( 'woocommerce_after_shop_loop_item_title', 'dd_loop_stock_note', 15 );

function dd_loop_stock_note() {
    global $product;
    if ( $product->is_in_stock() ) {
        echo '<span class="stock-note">In stock, ships today</span>';
    }
}

The price is printed at priority 10, so 15 puts the note right below it.

WooCommerce filter hooks

Filters change values: text, HTML, arrays. These are the ones I use most:

FilterWhat it changes
woocommerce_get_price_htmlThe price HTML
woocommerce_product_single_add_to_cart_textButton text on the product page
woocommerce_product_add_to_cart_textButton text on shop and category pages
woocommerce_product_tabsProduct tabs: add, remove, reorder
woocommerce_checkout_fieldsCheckout form fields (classic checkout)

Example 5. Change the button text

add_filter( 'woocommerce_product_single_add_to_cart_text', 'dd_add_to_cart_text', 10, 2 );

function dd_add_to_cart_text( $text, $product ) {
    if ( $product->is_type( 'simple' ) ) {
        return 'Buy now';
    }
    return $text;
}

The 10 is the priority and the 2 is how many arguments your function accepts. Without it, $product won’t be passed in.

The same idea works for tabs. For example, to remove the Reviews tab:

add_filter( 'woocommerce_product_tabs', 'dd_remove_reviews_tab', 98 );

function dd_remove_reviews_tab( $tabs ) {
    unset( $tabs['reviews'] );
    return $tabs;
}

Example 6. Remove extra fields from checkout

Fields like “Company” and “Address 2” are often useless. The postcode too, if you ship with a courier that doesn’t need it.

add_filter( 'woocommerce_checkout_fields', 'dd_checkout_fields' );

function dd_checkout_fields( $fields ) {
    unset( $fields['billing']['billing_company'] );
    unset( $fields['billing']['billing_address_2'] );
    unset( $fields['billing']['billing_postcode'] );
    return $fields;
}

Two warnings. First, if you calculate taxes or shipping by postcode, don’t remove it. Second, this filter works for the classic checkout (the shortcode). New WooCommerce stores use the Checkout block by default, and fields there are changed differently, through the block’s own API. Open your checkout page and see which one you have before writing code.

How to find the right WooCommerce hooks

You don’t need to guess names.

  1. Install Query Monitor. It has a “Hooks & Actions” panel that shows which hooks fired on the current page and which functions are attached.
  2. Open a template file in woocommerce/templates/ and search for do_action(. Those are all the places you can step in.
  3. The official WooCommerce hook reference lists all of them.

Mistakes to avoid

  • Code inside the WooCommerce plugin or the main theme. It disappears after an update.
  • Wrong priority. If a block shows up in the wrong place, the number is almost always the reason.
  • echo inside a filter. A filter must return the value, not print it.
  • Unescaped output. If the text comes from a field or a user, use esc_html() or wp_kses_post().
  • Checking only in your own browser. A cached page can show the old version. Clear the cache and test logged out.
  • Testing without a backup. A mistake in functions.php can take the site down, so keep a fresh backup and be ready to remove the snippet by FTP.

Summary

WooCommerce hooks are the simplest way to change a store without extra plugins: less code, less load, fewer problems on updates. If you need changes to an existing store or a new one built from scratch, see how I build WooCommerce stores. For how block themes change the way templates work, read the Full Site Editing guide.