How to Add a Customizable Field to a WooCommerce Product

UPDATED 9/29/2019: Noticed some code typos and updated some of the display sections for compatibility with WooCommmerce 3.7.

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 (This is an affiliate link).

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.

/**
 * Display input on single product page
 */
function kia_custom_option(){
    $value = isset( $_POST['_custom_option' ] ) ? sanitize_text_field( $_POST['_custom_option'] ) : '';
    printf( '<p><label>%s<input name="_custom_option" value="%s" /></label></p>', __( 'Enter your custom text', 'kia-plugin-textdomain' ), esc_attr( $value ) );
}
add_action( 'woocommerce_before_add_to_cart_button', 'kia_custom_option', 9 );Code language: PHP (php)

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.

/**
 * Validate when adding to cart
 * 
 * @param bool $passed
 * @param int $product_id
 * @param int $quantity
 * @return bool
 */
function kia_add_to_cart_validation( $passed, $product_id, $qty ){

    if( isset( $_POST['_custom_option'] ) && sanitize_text_field( $_POST['_custom_option'] ) === '' ){
        $product = wc_get_product( $product_id );
        wc_add_notice( sprintf( __( '%s cannot be added to the cart until you enter some custom text.', 'kia-plugin-textdomain' ), $product->get_title() ), 'error' );
        return false;
    }

    return $passed;

}
add_filter( 'woocommerce_add_to_cart_validation', 'kia_add_to_cart_validation', 10, 3 );Code language: PHP (php)

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.

/**
 * Add custom data to the cart item
 * 
 * @param array $cart_item
 * @param int $product_id
 * @return array
 */
function kia_add_cart_item_data( $cart_item, $product_id ){

    if( isset( $_POST['_custom_option'] ) ) {
        $cart_item['custom_option'] = sanitize_text_field( $_POST[ '_custom_option' ] );
    }

    return $cart_item;

}
add_filter( 'woocommerce_add_cart_item_data', 'kia_add_cart_item_data', 10, 2 );
Code language: PHP (php)

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.

/**
 * Load cart data from session
 * 
 * @param array $cart_item
 * @param array $other_data
 * @return array
 */
function kia_get_cart_item_from_session( $cart_item, $values ) {

    if ( isset( $values['custom_option'] ) ){
        $cart_item['custom_option'] = $values['custom_option'];
    }

    return $cart_item;

}
add_filter( 'woocommerce_get_cart_item_from_session', 'kia_get_cart_item_from_session', 20, 2 );Code language: PHP (php)

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.

/**
 * Add meta to order item
 * 
 * @param int $item_id
 * @param array $values
 * @return void
 */
function kia_add_order_item_meta( $item_id, $values ) {

    if ( ! empty( $values['custom_option'] ) ) {
        wc_add_order_item_meta( $item_id, 'custom_option', $values['custom_option'] );           
    }

}
add_action( 'woocommerce_add_order_item_meta', 'kia_add_order_item_meta', 10, 2 );Code language: PHP (php)

Updated for WooCommerce 3.0

/**
 * Add meta to order item
 * 
 * @param  WC_Order_Item  $order_item
 * @param  string         $cart_item_key
 * @param  array          $values The cart item values array.
 * @since 3.0.0
 */
function kia_add_order_item_meta( $order_item, $cart_item_key, $values ) {

    if ( ! empty( $values['custom_option'] ) ) {
    	$order_item->add_meta_data( 'custom_option', sanitize_text_field( $values[ 'custom_option' ] ), true );       
    }

}
add_action( 'woocommerce_checkout_create_order_line_item', 'kia_add_order_item_meta', 10, 3 );Code language: PHP (php)

Display all the Things!

Now that we actually have some usable data in the cart, it is time to display it to the customer. First, we’ll want to show it in the cart.

/**
 * Display entered value in cart
 * 
 * @param array $other_data
 * @param array $cart_item
 * @return array
 */
function kia_get_item_data( $other_data, $cart_item ) {

    if ( isset( $cart_item['custom_option'] ) ){

        $other_data[] = array(
            'key' => __( 'Your custom text', 'kia-plugin-textdomain' ),
            'display' => sanitize_text_field( $cart_item['custom_option'] )
        );

    }

    return $other_data;

}
add_filter( 'woocommerce_get_item_data', 'kia_get_item_data', 10, 2 );Code language: PHP (php)

Then we’ll want to show it in the order overview page, which should also be the same template shown in the My Account area and all emails too.

/**
 * Restore custom field to product meta when product retrieved for order item.
 * Meta fields will be automatically displayed if not prefixed with _
 * 
 * @param WC_Product $product 
 * @param WC_Order_Item_Product $order_item
 * @return array
 */
function kia_order_item_product( $product, $order_item ){

    if( $order_item->get_meta( 'custom_option' ) ){
        $product->add_meta_data( 'custom_option', $order_item->get_meta( 'custom_option' ), true );
    }

    return $product;

}
add_filter( 'woocommerce_order_item_product', 'kia_order_item_product', 10, 2 );Code language: PHP (php)

Update: Instead of having ‘order_item’ appear in the order table, we can customize the label with the following code:

/**
 * Customize the display of the meta key in order tables.
 * 
 * @param string $display_key
 * @param obj[] $meta
 * @param WC_Order_Item_Product $order_item
 * @return string
 */
function kia_order_item_display_meta_key( $display_key, $meta, $order_item ){

    if( $meta->key == 'custom_option' ){
        $display_key =  __( 'Your custom text', 'kia-plugin-textdomain' );
    }

    return $display_key;

}
add_filter( 'woocommerce_order_item_display_meta_key', 'kia_order_item_display_meta_key', 10, 3 );Code language: PHP (php)

Bonus, order again!

Should the customer want to order the exact same item with the exact same field we can do that too by adding the order item meta to the new cart item created when ordering again.

/**
 * Order Again
 * 
 * @param array $cart_item
 * @param WC_Order_Item_Product $order_item
 * @param WC_Order $order
 * @return array
 */
function kia_order_again_cart_item_data( $cart_item, $order_item, $order ){

    if( $order_item->get_meta( 'custom_option' ) ){
        $cart_item['custom_option'] = $order_item->get_meta( 'custom_option' );
    }

    return $cart_item;

}
add_filter( 'woocommerce_order_again_cart_item_data', 'kia_order_again_cart_item_data', 10, 3 );Code language: PHP (php)

And that’s it! You can download the entire thing here.

Plugin options

If you need more customization options and you don’t want to do it yourself there are several plugins you can try to build this without writing any code.

  1. Advanced Product Fields for WooCommerce by Studio Wombat
  2. Product Add-ons by Woo

23 thoughts on “How to Add a Customizable Field to a WooCommerce Product

  1. Rodolfo

    Legend, thanks a million Kathy!

    Side note, I suggest to replace “woocommerce_add_order_item_meta” with “wc_add_order_item_meta”

  2. Kenton

    Hi, this is absolutely amazing! Thanks so much. Everything works as expected however on the final overview page of the order as well as the email, instead of saying custom text: what was entered, is says “custom_option: what was entered.” I cannot seem to figure out why. Any suggestions? Thanks!

    1. kathy Post author

      Hi @Kenton, I noticed a few typos thanks to your comment, so I think those are fixed now. And dropped the email order meta snippet as the data is saved as meta on the order item and not on the order. But to your question, WooCommerce defaults to the meta key `custom_option` when displaying the meta. I’m not sure when they added the ability to filter that label, but it’s possible now (WC3.7) and I’ve added a snippet to account for it by filtering `woocommerce_order_item_display_meta_key`. I’ve updated the code above, but it’s also available as a complete plugin. I hope that helps!

  3. Benjamin

    Hi, just perfect and awesome but a little little mistake ! Options can’t be display in the cart for one reason :

    $cart_item[‘custom_option’] is declared many time and it missing an underscore “_ ” like the inital name=”_custom_option”.

    Like you say, “pay attention the input’s name. We’re going to be using that a lot.”

    Thanks again for all of this !

    1. kathy Post author

      Good catch! I recently updated this post and must’ve introduced this error then. Instead of adding the underscore everywhere, I’m going to remove it from the `woocommerce_order_again_cart_item_data` filter. In the DB and in the front-end input name it will have an underscore, but in the cart data array, it will not have an underscore prefix.

  4. Carlo

    Tried to use just the code to show the input field and it doesn’t work…

    A similar code instead “echo ”;” works.
    Maybe this guide is outdated? Looking at the date is seems really recent.

    Thanks in advance for your help.

  5. Bebopweb

    Hi,
    Thank you very much for your script. I have a little question. I changed the input to a textarea and I would like to have on each line and keep them in the cart, sent mails and backoffice but I can’t do this.
    Can you help me please ?
    Thanks for your answer
    Olivier

    1. kathy Post author

      Hi Olivier…. I am not sure what you mean by “I would like to have on each line and keep them in the cart”. The full code in the tutorial _should_ have the stored data on each product and have it appear in the cart, backend, emails, etc.

      For a textarea, `sanitize_text_field()` is probably _not_ the correct sanitization function to use. I would switch that to `wp_kses_post()`which will allow all the HTML that’s allowed in a normal blog post, but still remove scripts.

    1. kathy Post author

      Hi, you will need to change `` to ``. And you will watch to switch `sanitize_text_field` to `sanitize_text_area` when displaying the value of the text area and I think maybe `wp_kses_post()` when sanitizing to save the value. Hope that helps!

  6. Jafaruddeen Ansari

    Thank Kathy, you saved my day, can you please give any option to display or hide this custom field value from cart/checkout/order page by check/uncheck.

    1. kathy Post author

      Hi Jafaruddeen! Glad it helped. It’s not clear what you are asking about hiding/showing. You can add conditional logic to probably any of the functions I’ve written.

  7. Allan

    Hello, Thank you for this, but can you make it not mandatory and hide it if empty in the cart, order, checkout, and email?

    1. kathy Post author

      Yes, that’s all possible via conditional logic. I won’t be updating the tutorial any time soon, but if you wanted to pursue a small project you can contact me. Though fair warning, in that case, it would probably be cheaper to purchase product add-ons.

  8. Dero

    Hi, adding this plugin, added the custom text field to all my products. I’d like it to be added to only one product, how would I do that?
    If the text box is empty, the cannot add error is displayed, I’d like the box to be optional, what do I need to change for this?
    Wordpress 5.9.3

  9. Steven

    Thanks Kathy, this is a great foundation to my somewhat specific use case where I’m trying to include the selectWoo (Woo’s fork of select2) feature to allow users to add usernames of other customers (in our private shop) to the order. That way I think we can start keeping track of co-ownership of the cask shares our customers participate in.

Comments are closed.