Posts

In this WooCommerce tutorial I will be showing you how to add a custom field to the front-end of a WooCommerce product. We’ll be adding a text input that a customer could use to enter some special instructions or a custom inscription, etc. In theory, you could expand this to do all kinds of customizations (like allow the customer to upload an image), but this is a tutorial so let’s keep it kind of simple. Or in lieu of banging your head against a wall you could just buy WooCommerce Product Add-ons.

We’ll start with adding the input to the single product page template then add the custom text to the cart, order, and even the checkout emails!

Please note that all code could go in your theme’s functions.php but really, this is functionality, so please put it in a plugin! This is going to be pretty code-heavy, so if you need a refresher on actions and filters and the like then you might want to review the basics before diving in to this.

First step is to add the text input to the front end

You can technically add this input anywhere, but since it is a core WooCommerce hook, woocommerce_before_add_to_cart_button is about 99% likely to work with any theme. Nothing too special going on here. We’re just adding a text input. Pay attention the input’s name. We’re going to be using that a lot.

Validate and sanitize the input data

If your field is optional, then you can delete this function completely. Or you could modify it to validate however, you’d like. For simplicity’s sake I’ve triggered an error if the customer tries to add the item to the cart without filling in any custom text.

Add the custom data to the cart item

At first there’s a lot of mystery going on with the cart. Where the heck is that data coming from anyway? All the products are stored in an array in _$SESSION data. For the most part, Woo saves the product ID and the quantity and a handful of other things, but conveniently has a filter that will allow us to pass some of our own data to the cart item.

Preserve the Cart Data

The cart is reloaded from the $_SESSION on every page load. This must be a security feature, but I am not actually 100% sure. I do know that the first time I started messing around I didn’t understand why the previous function was adding the info to the cart, but as soon as I loaded the cart it disappeared. That drove me crazy for a bit until someone pointed out the woocommerce_get_cart_item_from_session filter. Basically, we’ll just check if we already had the data in the $cart_item array, and if so, maintain it.

Save the Custom Data On Checkout

WooCommerce has improved quite a bit in how it handles this data. Now we can call a simple woocommerce_add_order_item_meta() and it kind of acts like post meta, but for the item in this specific order. The data ends up in its own table.

First you need to register the new checkout fields. This is where I’ve added a current_user_can() to test if the current user has the appropriate capabilities to see these extra fields. I’m using the manage_options capability because it was easier for me to test.

You will note that I’ve done something different with your “Fan Stopped”, etc checkboxes. You don’t have to do this, but I was curious and procrastinating. WooCommerce doesn’t support a multi-check set of checkboxes, but it does support defining your own custom field types. So with the following we create a new type of form field:

Next we display the new fields on the checkout page…. but only if they exist, because harkening back to the first code block, they won’t be in the checkout fields array if the user doesn’t have the right permissions.

PHP

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

// Display the extra field on the checkout form

functionkia_extra_checkout_fields(){

$checkout=WC()->checkout();

if(isset($checkout->checkout_fields['extra_fields'])){?>

<div class="extra-fields">

<h3><?php_e('CSR Information');?></h3>

<?php

// because of this foreach, everything added to the array in the previous function will display automagically

Now you’ll probably need to add some CSS to make this look better, and you need a way to save the multicheck data. You can review my tutorial on how to save the rest, and perhaps figure out the multicheck on your own.

WooCommerce has a small army of filters and hooks that you could customize almost every aspect of the plugin. If you have a grouped product the default display method is by menu_order. But recently, someone wanted to display the grouped items by the date they were published.

This is very easily accomplished by filtering the $args being passed through the woocommerce_grouped_children_args filter. WooCommerce queries for the child products in the grouped product via WP_Query so you can essentially use any parameter supported by WP_Query. In this case, we only need to change the orderby parameter to date and since we’d like the most recent items first, we swap the order parameter to descending.

If you need help understanding filters, I wrote what I think is a pretty good tutorial on how to use filters. It took me a while to understand them, but once you do they are very powerful and let you make a lot of customizations.

If you aren’t seeing any changes, chances are that the grouped product’s children have already been stored in a transient. You will need to delete this transient to see changes right away. You can clear all WooCommerce product transients via the Admin. Navigate to WooCommerce>System Status>Tools and click on the button to clear transients.

When I am not coding WooCommerce-related things, my other career as an international athlete. From a purely logical standpoint it has been a terrible career choice because it tends to pay negatively and has put a lot of wear and tear on my body. There have been times when I’ve seriously considered buying a cane because my feet hurt so badly. There was also that time I dislocated my sterno-clavicular joint and couldn’t raise my arm over my head. My clavicles are permanently uneven now. But at the same time, I’ve represented my country in international sports for 10 years, which is something very few people get to do. WooCommerce definitely pays the bills, but as one of my teammates put it, handball is my side hustle.

Four defenders in frame? Someone must be open, but I’m so desperate for action shots that I will post one where I almost assuredly should have passed.

I play handball on the USA national team and recently, we placed third at the North American and Caribbean Championships in Puerto Rico, which qualified us to participate in the Pan-American Championships this month in Cuba. Unfortunately, getting to Cuba is not cheap and Handball is still almost completely unknown in the USA so we’re not bringing in the big sponsors. We’ve been training for these games for 2 years. Many of my teammates moved to Auburn, AL to train full-time together and I’ve been playing semi-professionally in France. There are 12 countries going to Cuba and 5 get to leave with a ticket to the World Championships!

We won bronze at the North American and Caribbean Championships! Check out the black eye I got from a flying headbutt!

So What’s the Deal

In honor of my “side hustle”, for the next TEN DAYS (ending 15 May) I am going to be putting my main hustle in service of handball. I will answer any WooCommerce question, or complete any small WooCommerce-related project in exchange for a donation to the USA Women’s Quest to Cuba. I’m not officially billing you, so what you donate is up to you, but please don’t expect me to write a huge 3rd-party integration for a $5 donation. We’ve only got 10 days, so things I can turn around for you in 1-3 hours is perfect.

Any question that you can’t figure out on your own, I will answer (or tell you I can’t). Any place you’re stuck trying to code your own solution, I will help you solve. Any tweak to your web store that you haven’t gotten around to finding a developer for, I’m here.

You get a little WooCommerce help and the satisfaction of knowing that you are supporting the group of really dedicated and hard-working ladies on Team USA.

I recently answered a few questions on Stack Overflow and they all seemed to related to how to change what products show up in the shop loop… or any other of the shop archives. I figured I would compile them altogether for reference and inspiration.

WooCommerce builds a custom query for products in its WC_Query class by hooking into the classic pre_get_posts hook and changing WordPress’s query parameters to get the desired products. WooCommerce then removes itself from the query afterwards. I’m not 100% sure of why, but I presume there is a good reason. It might be running in WooCommerce, but it is still a regular WordPress query and so the regular WP_Query Parameters apply.

Like most WooCommerce code there is a convenient action hook right in the middle of the product_query() method which will allow us to hook in and make whatever changes we might like to the product query. Note that $q is the query and $this is the WC_Query class instance.

PHP

1

2

do_action('woocommerce_product_query',$q,$this);

Example 1: Display Only On-Sale products in Shop

WooCommerce has a shortcode for displaying the most recent on-sale products. So I took a look at how it sets up it’s query args for get_posts(). Turns out WC has a built-in function that returns the IDs of any on-sale products: wc_get_product_ids_on_sale(). So we get grab those ids and then query for those specific posts using the post__in parameter of WP_Query.

As is typical in dealing with WordPress query objects we can use get() and set() methods to retrieve info about the query or set new parameters.

PHP

1

2

3

4

5

6

7

8

9

10

add_action('woocommerce_product_query','so_20990199_product_query');

functionso_20990199_product_query($q){

$product_ids_on_sale=wc_get_product_ids_on_sale();

$q->set('post__in',(array)$product_ids_on_sale);

}

Example 2: Hide Products that are Part of a Grouped Product

The person who asked this question has a lot of products that part of other grouped products and didn’t want to display the items individually and in their grouped versions. Turns out that items that are part of a group have the grouped product as their post_parent. Any grouped product (or other top-level, non-grouped product) will have 0 as a post parent. Therefore, we can set the query argument to require a post parent of 0 and effectively eliminate any grouped items.

PHP

1

2

3

4

5

6

add_action('woocommerce_product_query','so_27975262_product_query');

functionso_27975262_product_query($q){

$q->set('post_parent',0);

}

Example 3: Hide Specific Out of Stock Items

WooCommerce has a setting that allows you to hide all out of stock items from your store. But what if you don’t want a nuclear option and want to have a little finer control over which items are going to be hidden.

We can’t skip straight to the query, because WooCommerce doesn’t have this data. So we need to add some meta. You could use a custom field, but that isn’t as friendly as adding a little checkbox to the product data metabox. I thought it would be appropriate if we placed it near the stock status inputs. WooCommerce already has functions for creating most of the standard input types so we’ll use that to our advantage.

woocommerce_wp_checkbox(array('id'=>'_hide_if_out_of_stock','wrapper_class'=>'show_if_simple show_if_variable','label'=>__('Hide this product from archives when out of stock?','your-plugin-domain')));

}

Then we need to save this data. Normally, I’d save a checkbox as ‘yes’ versus ‘no’ like WooCommerce does. However, getting the product query correct (as you’ll see a little later on), required that the meta exist when you wanted to hide the item and not exist at all otherwise… hence the if/else update_post_meta() versus delete_post_meta()

And now we can get to the query. Because WooCommerce already has a few keys in its default meta query and the default meta query operator is AND (meaning my conditions had to be met in addition to what WooCommerce was already looking for), I couldn’t figure out a way to query for posts that had meta equal to a specific key. But because WordPress supports EXISTS and NOT EXISTS comparisons, we can look for posts that way. What I’ve done is in the case where you aren’t mass hiding all out of stock items via the plugin option, this code will modify the meta query so that any item that does not have the meta key _hide_if_out_of_stock will be shown. That is a counter-intuitive way of saying that any product where the box “hide when out of stock” is checked will be hidden.

PHP

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

add_action('woocommerce_product_query','so_27971630_product_query');

functionso_27971630_product_query($q){

$meta_query=$q->get('meta_query');

if(get_option('woocommerce_hide_out_of_stock_items')=='no'){

$meta_query[]=array(

'key'=>'_hide_if_out_of_stock',

'compare'=>'NOT EXISTS'

);

}

$q->set('meta_query',$meta_query);

}

Those are the three uses of modifying WooCommerce’s product query that I have encountered in the past few days. Anybody else doing anything interesting to the product query?