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:

woocommerce_default_catalog_orderby

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';
        break;
   case 'location':
        $args_sort_cw['orderby'] = 'meta_value';
        $args_sort_cw['order'] = 'asc';
        $args_sort_cw['meta_key'] = 'location';
        break;

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' ) )
return;
$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' );
endif;
}

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' ) )
return;
$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.

Gotchas

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;
  
  ob_start();

  $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:

<?php
 /**
 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>';     
the_field('test_field');   
}    
}

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:

<?php
 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' => '',
 ));
 endif;

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.

<?php
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.

<?php 
 // 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);
             $user->set_role('reseller');
         }
     }
 }

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>';
 }
 /**
 Verification 
 */
 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/
 */ 
 ///////////////////////////////
 // 1. ADD FIELDS
 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
 }
 ///////////////////////////////
 // 2. VALIDATE FIELDS
 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;
 }
 ///////////////////////////////
 // 3. SAVE FIELDS
 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 () {
         $('.company-field').slideToggle();
 });

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 ad-ons including the WooCommerce ad-on. This allows importing of all kinds of post types, not just WooCommerce products.

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.