Oxygen Builder & WooCommerce

Oxygen Builder , a great WordPress site builder that we use for several clients, offers a neat way to build websites using WordPress’ CMS while building all bypassing the general theme setup using the Oxygen Builder. It also has a WooCommerce addition that allows you to add WooCommerce Elements on your website and build product pages, category pages, cart and checkout pages.

Working with it thus far has been rather limiting in our experience however. We work with WooCommerce and multiple plugins or custom code to get things done that are needed for our clients such as:

  • extra fees at checkout
  • cart in menu
  • WooCommerce Add-Ons Ultimate to add extra child products to parent products
  • Abandoned Cart Plugin
  • Variation Swatches

There are just no options to build WooCommerce template layouts with ease. The options we have now just offer basic ways to set up these pages and do not allow fine grained setups of the different page templates. The other issue is that the plugins that tend to extend WooCommerce are attached to the limited builder blocks Oxygen Builder offers and cause layout and design issues.

WooCommerce Design Elements

What we miss most of the time are design elements and design sets for WooCommerce Templates. Oxygen does not offer any design sets for WooCommerce. It only offers some for general site setups besides their core design blocks. We do however seem to have other business that do offer design sets:

  • Oxy Elements $9 per month + unlimited sites and is for WooCommerce as well as general elements
  • Oxy Ultimate Woo Pack $149 unlimited site license / General pack is separate and $119 for life time support
  • Oxy Ninja Woocore €109 lifetime + unlimited sites & with Core general design sets only €178
  • Oxymade $149 lifetime + unlimited sites & Often bought with Oxymonster general design kits for $229

These all seems to help out setting up WooCommerce templates such as the checkout, cart, product, category pages. And all tend to offer general design sets besides WooCommerce sets. When you ask other Oxygen users you get some pro Oxy Elements, Oxy Ultimate and others in favor of Oxy Ninja or Oxymade.

I do not like monthly payments. I do however understand their setup. And besides, you can quit anytime and keep all they developed. I guess they try to developer a loyal audience that will stick around like most do for site builder SaaS. Not sure if it is sustainable but we shall see with the extension.

Then there is Ultimate, Ninja and Oxy Made. Oxy Ultimate seems the priciest for both general and WooCommerce elements so that leaves us Ninja and Oxy Made. Oxy These two seem to be close contenders. But let’s not be hasty and checkout the top 4 guys here: Oxy Elements, Oxy Ultimate, Oxy Ninja and Oxy Made.

Oxy Elements

So, Oxy Elements , what about these guys? Well it is a European based company as seen on their timezone choice on the contact page. They are the only ones it seems who offer a monthly fee package so stand out with this. That is $9 per month. They mention they offer

  • Template pages
  • Shop Template
  • My Account Template
  • Order tracking Template
  • Cart Template
  • Checkout Template
  • Single product

They do not talk about separate design blocks, but they do mention that they help adding icon cart, search bar, reviews bar, pro menu with code you can add to custom code plugin. They also show basic screenshots of the WooCommerce templates they help with layout wise. Overall not that crystal clear on the design blocks or kits in general . I miss more details on the design blocks , a real demo.

Oxy Ninja Core and Woocore

So as stated before we are interested in Oxy Ninja and their Core and Woocore packages as they do seem to offer solid pricing with a combo package for life and unlimited sites for only €178 currently. When you check their website it comes across as a modern website. They clearly spend most of their time on building, not on blogging. their documentation is reasonable though could be improved. So in general this good be done better.

When you want to see all the design sets they do not seem to offer a page or page with listed items. They however offer demos like this one for Core or this one for Woocore. These look pretty good in the case of Woocore seem to show you can generate custom checkout and cart pages as well as pages for other WooCommerce templates. In the case of core the demo allows you to load different design elements . Do like this much better and a shame this was not done for WooCommerce sets.

Oxy Ultimate

So what about our second contender Ultimate? How do they present themselves? I love the way they display the WooCommerce design elements they have on offer at the Woopack page. It looks good and is very clear. And they seems to have all we need with more on the way. If you want to buy their WooCommerce and general pack you have to pay $119 for general package and $149 for WooCommerce package so $168 in total. Even cheaper than Oxy Ninja even though they do not offer a combo deal. They are a strong believer of no bloat which is great, but I cannot test this until I have actual paid for and had a look at their code.

When you click on one of the icons that represent templates or elements they have on offer they show you YouTube videos on what you can exactly like this cart page builder video. These videos are pretty decent and show you how things are done.

Oxy Made + Monster

Oxy Made and Oxy Monster are also a combo package that offers all you need inside Oxygen Builder. They offer general design sets this way as well as WooCommerce design sets. This all in total for $229 so pricier then Ultimate and Ninja. These guys built Oxy Made with Tailwind CSS and do seem to offer a lot of design sets: 1000+ design blocks in 25 categories for Oxygen Builder.

Oxymade is the design kit here and Oxy Monster is design kits and framework. So why would we need both here? Well Oxy Monster is a CSS framework with over 500 utility classes. And besides that they offer a couple of ready made design kits that are like design packages for a general site setup with Oxygen . Now, Oxy Made. They offer over 1000+ design blocks or page elements. So these two together offer loads of blocks , several design lists and a CSS Framework based on Tailwind to work with. So pretty cool as well .

However , where do they showcase their WooCommerce blocks and design sets? Well their checkout kit is in the works, but not done yet. As for WooCommerce building block in Oxy Made. When we check preview there seem to be none.

Extension Choice

So what extension should you go for? Well we tend to believe the Oxy Made and Oxy Monster setup to be really neat But they do not help with WooCommerce at all. So that leaves us with Oxy Ultimate and Oxy Ninja as Elements is stil monthly and not suitable for us.

Our choice for now is with Oxy Ultimate as they offer clear options for WooCommerce, have nice videos explaining how things can be done and seem to have a really great and friendly developer taking care of things.

WooCommerce Layout Management

We also miss ways to split added code into its own design blocks or parts. Many plugins that offer additional functionality hook into a part of WooCommerce and are therefore automatically added to one or another core WooCommerce block you can add with Oxygen Builder

Add-On Ultimate hangs all its goodies on the price display part and because of that is is very hard to style the add-on pages. And though WooCommerce Add-On Ultimate offers tabbed display, table display and such the display is in general far from ideal. For this I have not found an ideal solution yet.

I guess in these cases you need the plugin developer to work on a solution that works better with Oxygen Builder. A lot of plugins work well with Divi and Elementor. There seems to however be less players in the field that care about the Oxygen Builder for now. An idea for a new product perhaps?

Winter Bash WooCommerce Sales Badge

To add a winter sale or Winter Bash badge we added this script to our must use plugin

// Hook into the 'wp_enqueue_scripts' action
add_action( 'wp_enqueue_scripts', 'custom_scripts' );
add_filter('woocommerce_sale_flash', 'woocommerce_custom_sale_text', 10, 3);
function woocommerce_custom_sale_text($text, $post, $_product)
return 'Winter Bash';

It basically adjusts the WooCommerce Sales badge text with the text added. This will apply to to all products on sale so if this was only needed for a specific product you will need to add a conditional block to wrap this filter in.

To add some fancier CSS to rotate the badge and show in attached to the corner of the product featured image move it to the right we used

/* Sales Badge */
.single-product #-product-images-186-10168 span.onsale {
padding-top: 15px;
padding-left: 50px;
padding-right: 50px;
padding-bottom: 15px;
background-color: #006437;
font-size: 16px;
border-radius: 0px;
position: absolute;
left: 460px;
width: 200px;
transform: rotate(45deg);

You probably need to change the selectors here of course. This as we used a general WooCommerce class followed by an Oxygen Builder class.

Also see https://www.cloudways.com/blog/change-sale-badge-text-in-woocommerce/ for basic setup.

Load WPML currency switcher next to WooCommerce Default Sorting

How to add something just before the default sorting of WooCommerce on product category pages. Well, that is not that easy you will find out.

General Filter Hook

There is a general method you can tweak:


But it is just for filtering the WooCommerce default sorting options and possible add new options.

See http://hookr.io/filters/woocommerce_default_catalog_orderby/

Adding Filters

So as Cloudways tells we can use:

function cw_add_postmeta_ordering_args( $args_sort_cw ) {
$cw_orderby_value = isset( $_GET['orderby'] ) ? wc_clean( $_GET['orderby'] ) :
    apply_filters( 'woocommerce_default_catalog_orderby', get_option( 'woocommerce_default_catalog_orderby' ) );
switch( $cw_orderby_value ) {
    case 'points_awarded':
        $args_sort_cw['orderby'] = 'meta_value_num';
        $args_sort_cw['order'] = 'desc';
        $args_sort_cw['meta_key'] = 'points';
   case 'location':
        $args_sort_cw['orderby'] = 'meta_value';
        $args_sort_cw['order'] = 'asc';
        $args_sort_cw['meta_key'] = 'location';

to sort by location and points . See https://www.cloudways.com/blog/woocommerce-product-sort-and-display/

Before or after Action Hooks?

But there seem to be no before or after default sorting hooks we can use. There are many for the product page as well as the category page. But one to add something just before sorting does not seem to exist. There is a filter, but there are no actions.

New Action Hook

Now, we could create our own do_action so we can hook to it. But we would then also need to add the hook to our template at the location where the ordering is being done.

function imwz_before_default_sorting () {
do_action ('imwz_before_default_sorting');

Then we would need to hook into that using

add_action('imwz_before_default_sorting', 'prod_cat_before_sorting')

And then we would need the actual method

function prod_cat_before_sorting () {
do_action('wcml_currency_switcher', array('format' => '%name% (%symbol%)'));

Child Templates

This you would however need to add to your product category or product page template. This cannot be added with ease in let’s say Oxygen Builder, Elementor on in basic WordPress using a plugin. It would need to be added to a child theme’s WooCommerce category and product templates. This is just a copy of the base template files you can find in the plugin and then use in your child theme.

Add Packaging Fee to Checkout

To add a packaging fee for your WooCommerce products on checkout you have many options. You could work with custom fields, additional pricing calculations, but in this post we will use hard coded actions instead.

WooCommerce Cart Calculate Fee with Add Fee

can use the following code

add_action('woocommerce_cart_calculate_fees', function() { 
if (is_admin() && !defined('DOING_AJAX')) { return; } 
WC()->cart->add_fee(__('Packaging Fee' 'txtdomain'), 5); 

This is a fixed price and not based on the price or cart total nor connected to shipping. Just a once off fee. Other options including this one can be found at White Pixel.

On the action and cart / recurring cart fees you can also check the WooCommerce documentation:

Unlike cart items, cart fees are non-persistent, this means third-party plugins which add fees need to be constantly hooked into the cart calculations, adding their fees each time the cart totals are calculated.

To do that WooCommerce provides the woocommerce_cart_calculate_fees hook, which third parties can use to add their fees.

Standard Cart / Checkout Surcharge incl Country Filter

Also see cart and checkout surcharge document where a similar snippet is used among others:

Add a standard $ value surcharge to all transactions in cart / checkout
add_action( 'woocommerce_cart_calculate_fees','wc_add_surcharge' );
function wc_add_surcharge() {
global $woocommerce;
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
$county = array('US');
// change the $fee to set the surcharge to a value to suit
$fee = 1.00;
if ( in_array( WC()->customer->get_shipping_country(), $county ) ) :
$woocommerce->cart->add_fee( 'Surcharge', $fee, true, 'standard' );

Do realize they also filter by country in this snippet. They do not just add a general surcharge fee for all locations.

WooCommerce also shared a percentage charge per transaction:

Add a 1% surcharge to your cart / checkout
change the $percentage to set the surcharge to a value to suit
add_action( 'woocommerce_cart_calculate_fees','woocommerce_custom_surcharge' );
function woocommerce_custom_surcharge() {
global $woocommerce;
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
$percentage = 0.01;
$surcharge = ( $woocommerce->cart->cart_contents_total + $woocommerce->cart->shipping_total ) * $percentage;
$woocommerce->cart->add_fee( 'Surcharge', $surcharge, true, '' );

which is related to one of the snippets provided by White Pixel.


You do have to remember that this option does not allow you do adjust the fee in the backend and so it is not a flexible fee. You could work with a custom field as well perhaps, but that will not be added to this post just yet.

In/Out of Stock Custom Messages

In/Out of Stock Custom Messages

What if you would like to show a certain text in a tab when stock is available and another when there is no stock. Well we could add a shortcode with an attribute, one with in stock and another without out of stock. But we would prefer to use one. One loading all text with an if else statement. But that would mean all text would be in the plugin or functions.php.

to add a shortcode we can use

 * Register in or out of stock text shortcode
 * @return null
function imwz_register_in_or_out_stock_text_shortcode() {
  add_shortcode( 'inoroutofstocktext', 'imwz_in_or_out_stock_text_check' );
add_action( 'init', 'imwz_register_in_or_out_stock_text_shortcode' );

Now, the actual function with text and if else statement would be like:

function imwz_in_or_out_stock_text_check () {
  global $product;

  $output = '';

  if ( ! $product->managing_stock() && ! $product->is_in_stock() ) {
      echo "2-4 dagen";
  elseif ($product->is_in_stock()) {
    echo "1-2 dagen";

  else {
    echo "nothing to see here"; // or return

  $output = ob_get_clean();
  // wp_reset_postdata();
  return $output;

For a live warning when adding more items to the cart than there are in stock see a jQuery solution at https://stackoverflow.com/questions/54452789/how-can-i-display-text-message-if-woocommerce-quantity-input-is-greater-than-sto as well as https://wordpress.org/plugins/woocommerce-cart-stock-reducer/ . Both just not solutions we want to use now due to many database requests.

VAT Based on User Role

No VAT If User Role is Company

Often you need to charge VAT or Dutch BTW for customers only and not for your vendors or companies shopping at your website. So how do you make sure that users with the role business see no VAT and customers or private persons do see VAT?

User Role Check

To check for specific role and apply a rule to it you can use this code:

## The (repetitive) conditional function based on your "special" user roles 
function is_special_user_role(){
  if ( ! is_user_logged_in() ) return false;

  $user = wp_get_current_user(); // current user
  $user_roles = $user->roles; // It's always an array (as a user can have many roles)

  // HERE your defined user roles
  $defined_user_roles = array( 'company' );

  if ( count( array_intersect( $user_roles, $defined_user_roles ) ) > 0 ) 
  return true;
  else return false; // ==> Added update here

VAT Exemption Check

Now, to check if a user role is tax exempt and then turn off VAT/BTW for that user role we can add this action hook:

## Function that will check for user role and turn off VAT/tax for that role
add_action( 'template_redirect', 'wc_diff_rate_for_user', 1 );
function wc_diff_rate_for_user() {
    // check for the user role and set the customer object to have no VAT
    if ( is_special_user_role() )
        WC()->customer->set_is_vat_exempt( true ); // Updated HERE

NB If you just need a display excl. vat and do not really need the user to be tax exempt this is not the setup you are looking for.

VAT Price Suffix

Function that removes the price suffix based on role. So with it you could hide the including tax title for certain user roles:

Function that removes the price suffix (inc. Tax) from variable products based on role
 add_filter( 'woocommerce_get_price_suffix', 'wc_get_price_suffix_filter', 10, 2 );
 function wc_get_price_suffix_filter( $price_display_suffix, $item ) {
     // check for the user role return blank if it matches
     if ( is_special_user_role() )
         $price_display_suffix = '';
 return $price_display_suffix;

NB https://stackoverflow.com/a/46678453/460885

NBB Great resource https://gist.github.com/jasperf/0568154ca7b4f8a8551b41d5d0a26b0e

Display Product Excluding VAT

So what if your products are all inclusive VAT? How do you then display the price for a certain user role excluding VAT? Well, you can add a price suffix at wp-admin/admin.php?page=wc-settings&tab=tax

Prijs Excl BTW {price_excluding_tax}

And then you work out a why to hide the price including VAT with CSS or otherwise. With CSS is is kind of hard as there are no unique classes for the basic price display nor for the discounted price display. For the discounted price this could work:

ins {

    visibility: hidden;

The same for del for the price itself, but would not remove the divs.

NB https://docs.woocommerce.com/document/setting-up-taxes-in-woocommerce/#section-10

Price Display Incl. & Excl. VAT at the same time

Another option would be code to load it separately in the product single template or with a shortcode. Here is a way to show the price with and without taxes and that at the same time.

function edit_price_display() {
     global $product;
     $price = $product->price;
     $price_incl_tax = $price + round($price * ( 21 / 100 ), 2);
     $price_incl_tax = number_format($price_incl_tax, 2, ",", ".");
     $price = number_format($price, 2, ",", ".");
     $display_price = '';     $display_price .= '€ ' . $price_incl_tax .' incl BTW';
     $display_price .= '
     $display_price .= '€ ' . $price .' excl BTW';
     $display_price .= '';
     echo $display_price;
 add_filter('woocommerce_get_price_html', 'edit_price_display', 10, 2);

And to only show in on the product single page we could use

function edit_price_display($price, $instance) {
     global $product;
  if(is_singular('product')) {     
  $price = $product->price;
  $price_incl_tax = $price + round($price * ( 21 / 100 ), 2);
  $price_incl_tax = number_format($price_incl_tax, 2, ",", ".");     
  $price = number_format($price, 2, ",", ".");     
  $display_price = '<span class="price">';     
  $display_price .= '<span class="amount">€ ' . $price_incl_tax .'<small class="woocommerce-price-suffix"> incl BTW</small></span>';     
  $display_price .= '<br>';     $display_price .= '<span class="amount">€ ' . $price .'<small class="woocommerce-price-suffix"> excl BTW</small></span>';     
  $display_price .= '</span>';     
  echo $display_price; } else {     echo $price;     }
 add_filter('woocommerce_get_price_html', 'edit_price_display', 10, 2);

NB Code may be malformatted. Check source for code with proper html tags!

NBB We could also then filter with another conditional by role. See further down for an example using just that.

Source Thomas Schmidt

WooCommerce Different Tax Rate by Role

As suggested by WooCommerce themselves we can use the woocommerce_product_tax_class filter to use a different tax rate for a user:

 Apply a different tax rate based on the user role.
 function wc_diff_rate_for_user( $tax_class, $product ) {
 if ( is_user_logged_in() && current_user_can( 'administrator' ) ) {
     $tax_class = 'Zero Rate';
 return $tax_class;
 add_filter( 'woocommerce_product_tax_class', 'wc_diff_rate_for_user', 1, 2 ); 

The administrator role used here as you can see and $tax_class is set to zero.

NB https://docs.woocommerce.com/document/setting-up-taxes-in-woocommerce/#section-19

Product Page Display Excl. VAT by Role

If you would like to only show the price excluding VAT or a user role and still have the user pay VAT in the end you can use the following code

Custom Fields in WooCommerce

One way or another working with WooCommerce you wind up needing custom fields and preferably Advanced Custom Fields or ACF fields. This tutorial will talk about (advanced) custom fields in WooCommerce and the many way these can be used. This might wind up being a long post and lots or resources will be used and referred to so bear with me.

Table of Contents

Basic Single Product Page Display

What if you want to add an ACF field with your own code to the theme? Well that is just like adding any other advanced custom field type to any template really. As shown at the beginning you just need to focus on the correct product type and field type and then load the data you entered in the backend properly there.

To add an ACF field to the single product page in WooCommerce after the summary you can use a basic setup like this:

// Add ACF Info to Product display page 
add_action( 'woocommerce_after_single_product_summary', "ACF_product_content", 10 ); 
function ACF_product_content(){      
echo '<h2> ACF Content </h2>';      
if (function_exists('the_field')){     
echo '<p>Woohoo, the_field function exists! </p>';     

It uses the WooCommerce woocommerce_after_single_product_summary hook to add information after the product description. And there are more hooks in WooCommerce for other locations of course.

NB The actual code to load the field in the backend is not mentioned here. This as that is quite easily done using the ACF GUI.

See https://support.advancedcustomfields.com/forums/topic/acf-with-woocommerce/

ACF & Custom Tabs

You can also use advanced custom fields to add custom tabs to WooCommerce. This is actually often needed. Many products tend to require extra information in a custom tab. Liquid Web wrote adding custom tabs with ACF. What they do is create a repeater field using a custom file added to the theme. Here is a forked snippet for the repeater field:

 if( function_exists('acf_add_local_field_group') ):
 acf_add_local_field_group(array (
     'key' => 'acf_product_options',
     'title' => 'Product Options',
     'fields' => array (
         array (
             'key' => 'acf_product_options_tabbedcontent_label',
             'label' => 'Tabbed Content',
             'name' => '',
             'type' => 'tab',
             'instructions' => '',
             'required' => 0,
             'conditional_logic' => 0,
             'wrapper' => array (
                 'width' => '',
                 'class' => '',
                 'id' => '',
             'placement' => 'top',
             'endpoint' => 0,
         array (
             'key' => 'acf_product_options_tabbedcontent_tabs',
             'label' => 'Tabs',
             'name' => 'tabs',
             'type' => 'repeater',
             'instructions' => '',
             'required' => 0,
             'conditional_logic' => 0,
             'wrapper' => array (
                 'width' => '',
                 'class' => '',
                 'id' => '',
             'min' => '',
             'max' => '',
             'layout' => 'row',
             'button_label' => 'Add Tab',
             'sub_fields' => array (
                 array (
                     'key' => 'acf_product_options_tabbedcontent_tab_title',
                     'label' => 'Tab Title',
                     'name' => 'tab_title',
                     'type' => 'text',
                     'instructions' => '',
                     'required' => 0,
                     'conditional_logic' => 0,
                     'wrapper' => array (
                         'width' => '',
                         'class' => '',
                         'id' => '',
                     'default_value' => '',
                     'placeholder' => '',
                     'prepend' => '',
                     'append' => '',
                     'maxlength' => '',
                     'readonly' => 0,
                     'disabled' => 0,
                 array (
                     'key' => 'acf_product_options_tabbedcontent_tab_content',
                     'label' => 'Tab Content',
                     'name' => 'tab_content',
                     'type' => 'wysiwyg',
                     'instructions' => '',
                     'required' => 0,
                     'conditional_logic' => 0,
                     'wrapper' => array (
                         'width' => '',
                         'class' => '',
                         'id' => '',
                     'default_value' => '',
                     'tabs' => 'all',
                     'toolbar' => 'full',
                     'media_upload' => 1,
     'location' => array (
         array (
             array (
                 'param' => 'post_type',
                 'operator' => '==',
                 'value' => 'product',
     'menu_order' => 0,
     'position' => 'normal',
     'style' => 'default',
     'label_placement' => 'top',
     'instruction_placement' => 'label',
     'hide_on_screen' => '',

Then they load the custom tab with another function in functions.php. This code adds the extra WooCommerce product tab and then loads the repeater field inside the tab. Here is a forked snippet for this WooCommerce product tab addition.

function hwid_load_custom_tab( $tab_key, $tab_info ) {
	echo apply_filters( 'the_content', $tab_info['tabContent'] );

function hwid_add_content_tabs( $tabs ) {

	global $post;

	$custom_tabs = get_field( 'tabs', $post->ID );

	foreach( $custom_tabs as $index => $tab ) {
		$tabs['customTab-' . $index] = array(
			'title' => $tab['tab_title'],
			'priority' => 20 + $index,
			'tabContent' => $tab['tab_content'],
			'callback' => 'hwid_load_custom_tab'

	return $tabs;

add_filter( 'woocommerce_product_tabs', 'hwid_add_content_tabs' );

NB Snippets made by AJ Morrris https://gist.github.com/ajmorris for Liquid Web

Booster also allows you to add custom tabs. See https://booster.io/features/woocommerce-custom-product-tabs/ . You can choose to add tabs for all product pages or for specific ones.

ACF WooCommerce Order Form

If you would like to display additional information about your products in checkout / at the order form you can read about at WP Major. Major focusses on getting ACF fields in the order form and they use the WooCommerce Product Table plugin. But it does explain how to create them for WooCommerce products so that helps

Remember, for this tutorial we’re mainly focused on taking that custom field data and getting it into a WooCommerce order form ..

Divi & Advanced Custom Fields

The Elegant Themes Divi theme as an ACF module these days. It does however not work with all the custom field types yet. It just works with single fields, tables and repeater fields.

However, since the end of 2018 Divi Builder has dynamic content:

Not only does Divi support that use of standard dynamic WordPress content, it also supports the use of custom field data. Whether you have created your own custom fields, or registered a new custom field with a plugin like Advanced Custom Fields, that dynamic data can now be used within the Divi Builder and connected to any module content area.

Advanced Custom Fields to WooCommerce Attributes

Sometimes you need to add a custom field to a product attribute.

A third and important way to group products is to use attributes. There are two uses of this data type that are relevant for WooCommerce: WooCommerce widgets and variable products

Jordan Smith came up with some nice code for that. Snippet forked and added below. This code you can add to your child theme or basic theme’s functions.php. It ads an ACF custom rule type, rule values and then ads it to product attributes.

 // Adds a custom rule type.
 add_filter( 'acf/location/rule_types', function( $choices ){
     $choices[ __("Other",'acf') ]['wc_prod_attr'] = 'WC Product Attribute';
     return $choices;
 } );
 // Adds custom rule values.
 add_filter( 'acf/location/rule_values/wc_prod_attr', function( $choices ){
     foreach ( wc_get_attribute_taxonomies() as $attr ) {
         $pa_name = wc_attribute_taxonomy_name( $attr->attribute_name );
         $choices[ $pa_name ] = $attr->attribute_label;
     return $choices;
 } );
 // Matching the custom rule.
 add_filter( 'acf/location/rule_match/wc_prod_attr', function( $match, $rule, $options ){
     if ( isset( $options['taxonomy'] ) ) {
         if ( '==' === $rule['operator'] ) {
             $match = $rule['value'] === $options['taxonomy'];
         } elseif ( '!=' === $rule['operator'] ) {
             $match = $rule['value'] !== $options['taxonomy'];
     return $match;
 }, 10, 3 );

ACF Field WooCommerce Category

Adding an advanced custom field to a WooCommerce category is similar in ways to adding one to an attribute:

// step 1 add a location rule type
    add_filter('acf/location/rule_types', 'acf_wc_product_type_rule_type');
    function acf_wc_product_type_rule_type($choices) {
      // first add the "Product" Category if it does not exist
      // this will be a place to put all custom rules assocaited with woocommerce
      // the reason for checking to see if it exists or not first
      // is just in case another custom rule is added
      if (!isset($choices['Product'])) {
        $choices['Product'] = array();
      // now add the 'Category' rule to it
      if (!isset($choices['Product']['product_cat'])) {
        // product_cat is the taxonomy name for woocommerce products
        $choices['Product']['product_cat_term'] = 'Product Category Term';
      return $choices;
    // step 2 skip custom rule operators, not needed
    // step 3 add custom rule values
    add_filter('acf/location/rule_values/product_cat_term', 'acf_wc_product_type_rule_values');
    function acf_wc_product_type_rule_values($choices) {
      // basically we need to get an list of all product categories
      // and put the into an array for choices
      $args = array(
        'taxonomy' => 'product_cat',
        'hide_empty' => false
      $terms = get_terms($args);
      foreach ($terms as $term) {
        $choices[$term->term_id] = $term->name;
      return $choices;
    // step 4, rule match
    add_filter('acf/location/rule_match/product_cat_term', 'acf_wc_product_type_rule_match', 10, 3);
    function acf_wc_product_type_rule_match($match, $rule, $options) {
      if (!isset($_GET['tag_ID'])) {
        // tag id is not set
        return $match;
      if ($rule['operator'] == '==') {
        $match = ($rule['value'] == $_GET['tag_ID']);
      } else {
        $match = !($rule['value'] == $_GET['tag_ID']);
      return $match;

NB Code course John Huebner ACF

ACF Below Product Image

To display an advanced custom field below the product image can be done with relative ease. We found a snippet at Business Bloomer by Rodolfo Melogi as a nice example:

* @snippet       Display Advanced Custom Fields @ Single Product - WooCommerce
* @how-to        Get CustomizeWoo.com FREE
* @sourcecode    https://businessbloomer.com/?p=22015
* @author        Rodolfo Melogli
* @compatible    WooCommerce 3.5.7
* @donate $9     https://businessbloomer.com/bloomer-armada/
add_action( 'woocommerce_product_thumbnails', 'bbloomer_display_acf_field_under_images', 30 );
function bbloomer_display_acf_field_under_images() {
echo '<b>Trade Price:</b> ' . get_field('trade');
// Note: 'trade' is the slug of the ACF

It uses the woocommerce_product_thumbnails hook to add elements below the product thumbnails.

Product Page & Product Table Plugin

To display an ACF field on a product page you can also use ACF to create the fields and the WooCommerce Product Table Plugin. You can read all about it at Barn2 . You can use this plugin to show a lot of product data on top of standard ones.

  • Product image, name, price
  • Short or long description
  • Categories and tags
  • Attributes and variations
  • Star rating from reviews
  • Embedded audio and video

Product Page Admin Only Note

Sometimes the end user wants to add notes in the backend for his use only. A field that is only shown in the admin area. In the admin area for a specific product. How would you go about this? Well, you do of course not have to print / display fields in the frontend so if you do not load them there you can just create them using the advanced field interface. Another way, if somehow your theme autoloads all ACF fields, is to hide them frontend with CSS.

I would use a text area or a field as ACF field type if the note is very short. That would suffice to add a note in the backend.

WC Register Form ACF Fields

To add fields to the WooCommerce Account registration form you could use https://wordpress.org/plugins/acf-woocommerce-account-fields/ . For us it did not work well though. Seems to be outdated somewhat.

You can also add custom ones using your own code. See https://stackoverflow.com/a/49054519/460885 where the awesome LoicTheAztec use the WooCommerce hook woocommerce_register_form to add fields and where he does validation as well.

And to save the form custom fields as well we use the following below as shown in SO thread:

// To save WooCommerce registration form custom fields.
 add_action( 'woocommerce_created_customer', 'wc_save_registration_form_fields' );
 function wc_save_registration_form_fields( $customer_id ) {
     if ( isset($_POST['role']) ) {
         if( $_POST['role'] == 'reseller' ){
             $user = new WP_User($customer_id);

NB This is similar to https://wpvilla.in/assign-specific-role-on-registration/

WooCommerce Checkout Fields

Here is another example showing a field at the end of the WooCommerce registration form’s notes or on checkout:

 Add custom fields to user / checkout
 add_action( 'woocommerce_after_order_notes', 'my_custom_checkout_field' ); 
 function my_custom_checkout_field( $checkout ) {
 echo '<div id="bv_custom_checkout_field"><h2>Measurements</h2>'; /* Weight */ woocommerce_form_field( 'weight_customer', array(     'type'          => 'text',     'class'         => array('my-class form-row-wide'),     'label'         => __('Your weight'),     'placeholder'   => __('Your weight'), ), get_user_meta(  get_current_user_id(),'weight_customer' , true  ) ); echo '</div>';
 add_action('woocommerce_checkout_process', 'my_custom_checkout_field_process'); 
 function my_custom_checkout_field_process() {
     // Check 
     if ( ! $_POST['weight_customer'] )
         wc_add_notice( __( 'Do not forget weight.' ), 'error' );
 Update field
 add_action( 'woocommerce_checkout_update_order_meta', 'my_custom_checkout_field_update_order_meta' ); 
 function my_custom_checkout_field_update_order_meta( $order_id ) {
     if ( ! empty( $_POST['weight_customer'] ) ) {
         update_user_meta( get_current_user_id(), 'weight_customer', sanitize_text_field( $_POST['weight_customer'], '' ));

Here woocommerce_after_order_notes and woocommerce_checkout_update_order_meta are the hooks used. So data is added after the order notes and the woocommerce checkout update order meta hook is used to store the extra fields. For other location see a good visual guide at https://businessbloomer.com/woocommerce-visual-hook-guide-checkout-page/ .

Snippet source Max @ ACF Forum

Custom Checkout Fields

If you need to make tweak to fields at /checkout you can use stuff discussed at https://docs.woocommerce.com/document/tutorial-customising-checkout-fields-using-actions-and-filters/ . To for example change the order comment placeholder you use:

// Hook in
add_filter( 'woocommerce_checkout_fields' , 'custom_override_checkout_fields' );

// Our hooked in function - $fields is passed via the filter!
function custom_override_checkout_fields( $fields ) {
     $fields['order']['order_comments']['placeholder'] = 'My new placeholder';
     return $fields;

Now if you want to add more billing fields and or add them before or after exisitng you can use something like

 * Simple checkout field addition example.
 * @param  array $fields List of existing billing fields.
 * @return array         List of modified billing fields.
function jeroensormani_add_checkout_fields( $fields ) {
  $fields['billing_FIELD_ID'] = array(
      'label'        => __( 'FIELD LABEL' ),
      'type'        => 'text',
      'class'        => array( 'form-row-wide' ),
      'priority'     => 35,
      'required'     => true,

  return $fields;
add_filter( 'woocommerce_billing_fields', 'jeroensormani_add_checkout_fields' );

If you want to position them you need to know the priorities of the current ones:

  • First name – 10
  • Last name – 20
  • Company name – 30
  • Country – 40
  • Street address – 50
  • Apartment, suite, unit etc. (optional) – 60
  • Town / City – 70
  • State – 80
  • Postcode / ZIP – 90
  • Phone – 100
  • Email – 110

Also, do not forget validation. Here a basic one for checking if a real number is entered. Good for a VAT number for example

 * Add custom field validation for BTW or KvK Number
function js_custom_checkout_field_validation( $data, $errors ) {
  foreach ( WC()->checkout()->get_checkout_fields() as $fieldset_key => $fieldset ) {
      foreach ( $fieldset as $key => $field ) {

          if ( isset( $field['validate'] ) && in_array( 'btw-number', $field['validate'] ) ) {
              if ( ! empty( $data[ $key ] ) && ! preg_match( '/[a-z0-9]{10}/', $data[ $key ] ) ) {
                  $errors->add( 'validation', 'Looks like your club number is invalid.' );

add_action( 'woocommerce_after_checkout_validation', 'js_custom_checkout_field_validation', 10, 2 );

NB source Jeroen Sormani

NBB Checkout Manager Plugin is very useful too although paid if in need of conditionals

NBBB There is Also WooCommerce Booster https://booster.io/features/woocommerce-checkout-customization/ , but no conditionals offered.

My Account / WooCommerce Registration

Like any WooCommerce page there are multiple hooks to adjust or add things to /my-account. See https://businessbloomer.com/woocommerce-visual-hook-guide-account-pages/.

To adjust or add more fields things are more complicated like for the checkout, but this is also possible. Business Bloomer has a snippet to add first and last name for example:

 @snippet       Add First & Last Name to My Account Register Form - WooCommerce
 @how-to        Get CustomizeWoo.com FREE
 @sourcecode    https://businessbloomer.com/?p=21974
 @author        Rodolfo Melogli
 @credits       Claudio SM Web
 @compatible    WC 3.5.2
 @donate $9     https://businessbloomer.com/bloomer-armada/
 add_action( 'woocommerce_register_form_start', 'bbloomer_add_name_woo_account_registration' );
 function bbloomer_add_name_woo_account_registration() {
 <p class="form-row form-row-first"> <label for="reg_billing_first_name"><?php _e( 'First name', 'woocommerce' ); ?> <span class="required">*</span></label> <input type="text" class="input-text" name="billing_first_name" id="reg_billing_first_name" value="<?php if ( ! empty( $_POST['billing_first_name'] ) ) esc_attr_e( $_POST['billing_first_name'] ); ?>" /> </p> <p class="form-row form-row-last"> <label for="reg_billing_last_name"><?php _e( 'Last name', 'woocommerce' ); ?> <span class="required">*</span></label> <input type="text" class="input-text" name="billing_last_name" id="reg_billing_last_name" value="<?php if ( ! empty( $_POST['billing_last_name'] ) ) esc_attr_e( $_POST['billing_last_name'] ); ?>" /> </p> <div class="clear"></div> <?php
 add_filter( 'woocommerce_registration_errors', 'bbloomer_validate_name_fields', 10, 3 );
 function bbloomer_validate_name_fields( $errors, $username, $email ) {
     if ( isset( $_POST['billing_first_name'] ) && empty( $_POST['billing_first_name'] ) ) {
         $errors->add( 'billing_first_name_error', ( 'Error: First name is required!', 'woocommerce' ) );
     if ( isset( $_POST['billing_last_name'] ) && empty( $_POST['billing_last_name'] ) ) {
         $errors->add( 'billing_last_name_error', ( 'Error: Last name is required!.', 'woocommerce' ) );
     return $errors;
 add_action( 'woocommerce_created_customer', 'bbloomer_save_name_fields' );
 function bbloomer_save_name_fields( $customer_id ) {
     if ( isset( $_POST['billing_first_name'] ) ) {
         update_user_meta( $customer_id, 'billing_first_name', sanitize_text_field( $_POST['billing_first_name'] ) );
         update_user_meta( $customer_id, 'first_name', sanitize_text_field($_POST['billing_first_name']) );
     if ( isset( $_POST['billing_last_name'] ) ) {
         update_user_meta( $customer_id, 'billing_last_name', sanitize_text_field( $_POST['billing_last_name'] ) );
         update_user_meta( $customer_id, 'last_name', sanitize_text_field($_POST['billing_last_name']) );

NB Also see https://github.com/woocommerce/woocommerce/issues/7667 and https://www.cloudways.com/blog/add-woocommerce-registration-form-fields/

To work with conditionals things get more complicated and of course radio buttons or select boxes are also a bit tougher still. You could use jQuery like this for example showing a field when radio button toggled:

$('.customer-type-radio input[type="radio"]').on('click', function () {

For code above by Business Bloomer you would need to focus on other html fields of course, but often after making choices like checkboxes or radio buttons you would like to toggle other fields.

WC Booster has some basic options including adding a user role select box https://booster.io/features/woocommerce-my-account/ . Does not seem to offer custom fields though.

There is also Yithemes Yith Woocommerce Customiz My Account Page . Does cost another €54.99. But that is really for the my-account overview for logged in users. For the WooCommerce Registration Form https://woocommerce.com/products/custom-user-registration-fields-for-woocommerce/ is recommended. It costs $49 USD.

Shop Page ACF

If you would like to add a new ACF location to do stuff on the WooCommerce Shop Page you can use

add_filter( 'acf/location/rule_values/page_type', function ( $choices ) {
  $choices['woo_shop_page'] = 'WooCommerce Shop Page';
  return $choices;
add_filter( 'acf/location/rule_match/page_type', function ( $match, $rule, $options ) {
  if ( $rule['value'] == 'woo_shop_page' && isset( $options['post_id'] ) ){
     if ( $rule['operator'] == '==' ){
       $match = ( $options['post_id'] == wc_get_page_id( 'shop' ) );
     if ( $rule['operator'] == '!=' ){
       $match = ( $options['post_id'] != wc_get_page_id( 'shop' ) );
  return $match;
}, 10, 3 );

Import 2000+ products including cross-sells

So you want to import a couple of thousand WooCommerce products and you want them imported included attached cross-sell. This as you need to have each product shown with cross-sells as thumbnails below each product. So what do you do? What plugin options are out there? Well, WP All Import comes to mind right away, but there is also Product Import Export for WooCommerce by Webtoffee. Let’s discuss them here.

Product Import Export for WooCommerce

Product import export plugin allows you to import or export WooCommerce simple products. If you want to import other product types such as variable products you will need the premium version which costs $69 for a single site license.

Product Import Export for WooCommerce by Webtoffee allows attaching cross-sells to products as well on import by settings things properly during import:

Use SKU to link up-sells and cross-sells: Check this option to use SKU to link the up-sells and cross-sells of a product. If this option is not checked, up-sells and cross-sells of a product will be linked using the Product ID.

WP All Import with WooCommerce Addon

WP All Import is the most famous import export tool in the WordPress arena and it a really nice tool made by the developer that also built Oxygen. WP Import / Export + All ad-ons costs you $249 USD. This will allow exporting as well as importing and using all add-ons including the WooCommerce ad-on. This allows importing of all kinds of post types, not just WooCommerce products.

This means you can import products with product cross sells and up sells as mentioned here below. You just need to prepare all properly on import.

WP All Import Cross Sells and Up Sells

Also see another example on how to link up sells and cross sells at Nexcess using WP All Import. It shows how you can connect and import not just the products but the up sells and cross sells as well.

In the linked products tab, you can import the up-sells and cross-sells for each product. You can match up-sells and cross-sells by SKU, ID, or Product Name. WP All Import will search for the products in your WooCommerce shop and automatically add their IDs to these fields for you.

So on import you need to refer to these up sells or cross sells using curly braces syntax. And in the WP All Import documentation and their videos you can read or see more about this. When I do have more time I will add more information.

Similar Products on Single Product Page

Similar Products on Single Product Page

For a particular client we wanted to offer very similar products as thumbnail links under products. The linked products would be similar products. They would however have to be separate products offering differences in color, fabric and pricing. They would also have a sample on offer each of them.

Variable Product?

Variable products do offer all the freedom in size, color and other attributes and even allow you to use variable prices. But as we really wanted to have independent products and even sell a sample per product this was not ideal at all. That is why a variable product was not a good choice.

Cross-Sell Linked Products

A cross sell was appealing as it would allow this linking and allow related products to stay intact as option below each product with cross-sell or linked product thumbnails. Here a little summary of the options for product linking in general.

Cross Sells are products that you would like to sell besides other products. They are often complementary items but they don’t have to be. In our case they would be more like related products. Related products are products related by tag or categorie. An option we wanted to keep open. So we did not want to use them as thumbnails below products of choice. Up-sells are products that tend to be pricier you try to get people interested in while looking at another product. Products you recommend so to speak. We will not be using them for now.

Cross-Sell Display

So as you can see cross-sells seemed to be the best choice. Now we wanted them to load on each product page where need be with ease and if a cross-sell would be removed or changed the thumbnail and link would have to be removed or updated. Normally cross-sells are loaded when you are at the cart page. And we wanted them on the single product page.

WooCommerce Cross Sell Products Shortcode

The Plugin WooCommerce Cross Sell Products Display allows the display of cross-sell products using a shortcode on single product pages or pretty much anywhere you would need them shown. It basically uses this code to get the cross-sell ids:

$crosssells = get_post_meta( $product_id, '_crosssell_ids', true);

There is even a carousel options for upsells, cross-sells and related products. Example shortcode here below:

[wcsp_cross_sell orderby=”rand” order=”ASC” product_num=”5″ display_columns=”3″ title=”Some title..” product_id=”Product ID” class=”class-1 class-2 class-3″]

Advanced Field Integration

If you need to load the shortcode in an advanced custom field as you want to easily load cross-sell on product pages you can do so as well. You can simply use the following code in your template:

do_shortcode( get_field('FIELDNAME') );

or allow it more broadly for certain field types inside functions.php using

add_filter('acf/format_value/type=textarea', 'do_shortcode');

You can of course pick another field type as well of course.