Posted on

WooCommerce: How to Add a Custom Checkout Field (PHP)

A client of mine runs online courses for acupuncturists via Sensei LMS. US Law requires an “Acupuncture Registration Number” in order for students to get the final online certificate. So, this task was a little bit more complex than usual as it had two major issues:

1) This new checkout field does not belong to billing or shipping (order information), but instead it’s a unique user field that needs to be saved and retrieved multiple times if necessary

2) This field does need to be shown at checkout only if a certain product category is in the cart (“online courses”, as opposed to e.g. “books”)

So, here’s how you do it – hope it helps you understand that anything is possible via PHP!

WooCommerce: Add Conditional User Field @ Checkout
WooCommerce: Add Conditional User Field @ Checkout

PHP Snippet (Part 1 of 5): Add User Field at Checkout (display only if category is in the Cart)


/**
 * @snippet       Add User Field Conditionally @ WooCommerce Checkout Page
 * @how-to        Watch tutorial @ https://businessbloomer.com/?p=19055
 * @sourcecode    https://businessbloomer.com/?p=20560
 * @author        Rodolfo Melogli
 * @testedwith    WooCommerce 3.5.1
 * @donate $9     https://businessbloomer.com/bloomer-armada/
 */

add_action( 'woocommerce_after_checkout_billing_form', 'bbloomer_add_acu_no_if_online_course' );

function bbloomer_add_acu_no_if_online_course( $checkout ) {

// see if user has a previously saved Acupuncture #

$current_user = wp_get_current_user();
$saved_acu_no = $current_user->student_acu_no;

// echo field only if bloomer_check_product_category() is true

if( bloomer_check_product_category() ){ 
echo '<div id="student_acu_no">';  
woocommerce_form_field( 'student_acu_no', array(        
'type'          => 'text',        
'class'         => array('student_acu_no form-row-wide'),        
'label'         => __('State Acupuncture License #'),        
'placeholder'   => __('CA12345678'),        
'required'   => true,        
'default'   => $saved_acu_no,        ), 
$checkout->get_value( 'student_acu_no' )); 
echo '</div>';
}

}

// Function that returns true/false if category is in the cart
// Is called by function bbloomer_add_acu_no_if_online_course()

function bloomer_check_product_category(){    
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {        
$product_id   = apply_filters( 'woocommerce_cart_item_product_id', $cart_item['product_id'], $cart_item, $cart_item_key );          
if( bbloomer_is_category_18_in_cart( $product_id ) ){            
return  true;               
}    
}   
return false;
}

// Function that specifies category ID for bloomer_check_product_category()
// Is called by function bloomer_check_product_category()

function bbloomer_is_category_18_in_cart( $product_id ){
// ID 18 = online courses
return has_term( 18, 'product_cat', get_post( $product_id ) );
}

PHP Snippet (Part 2 of 5): Validate New Checkout Field

Now, once the checkout is processed, we want at least to make sure field is not empty. Remember, we echoed a field with the option [‘required’ => true] which means we require this field to be filled out!

Here’s how we can guarantee to show an error message if field is empty:


// Validate Checkout Field

add_action('woocommerce_checkout_process', 'bbloomer_validate_new_checkout_field');

function bbloomer_validate_new_checkout_field() {    

if ( ! $_POST['student_acu_no'] )        
wc_add_notice( __( 'Please enter your Acupuncture Licence number' ), 'error' );

}

PHP Snippet (Part 3 of 5): Save New Checkout Field Into User Meta

If validation is successful, we want of course to save the Acupuncture Licence number into the User database (called “User meta”). This is pretty simple:


// Save Field Into User Meta

add_action( 'woocommerce_checkout_update_user_meta', 'bbloomer_checkout_field_update_user_meta' );

function bbloomer_checkout_field_update_user_meta( $user_id ) {	
if ( $user_id && $_POST['student_acu_no'] ) update_user_meta( $user_id, 'student_acu_no', sanitize_text_field($_POST['student_acu_no']) );
}

PHP Snippet (Part 4 of 5): Show New Field In The User Profile Page

Also, it’s evident we want to let the Admin change this field when necessary. First, we need to display it in the User Profile Page. Finally, we need to make sure to update the user meta when this is updated:


// Display User Field @ User Profile

add_action( 'show_user_profile', 'bbloomer_show_user_extra_field' );
add_action( 'edit_user_profile', 'bbloomer_show_user_extra_field' );    

function bbloomer_show_user_extra_field( $user ){ ?>    
<h3>Additional Fields</h3>    
<table class="form-table">        
<tr>            
<th><label for="student_acu_no">Acu #</label></th>            
<td><input type="text" name="student_acu_no" value="<?php echo esc_attr(get_the_author_meta( 'student_acu_no', $user->ID )); ?>" class="regular-text" /></td>        
</tr>    
</table><?php
}  

// Save User Field When Changed From the Profile Page

add_action( 'personal_options_update', 'bbloomer_save_extra_fields' );    
add_action( 'edit_user_profile_update', 'bbloomer_save_extra_fields' );    

function bbloomer_save_extra_fields( $user_id ){        
update_user_meta( $user_id,'student_acu_no', sanitize_text_field( $_POST['student_acu_no'] ) );    
} 

PHP Snippet (Part 5 of 5): Save & Show New Field In The WooCommerce Order Details

Besides, we want to show the Acupuncture number on the Order details. Admin needs to double check the number is correct before issuing a certificate.


// Update order meta with field value

add_action( 'woocommerce_checkout_update_order_meta', 'bbloomer_custom_checkout_field_update_user_meta' );

function bbloomer_custom_checkout_field_update_user_meta( $order_id ) {	
if ( $_POST['student_acu_no'] ) update_post_meta( $order_id, '_student_acu_no', sanitize_text_field($_POST['student_acu_no']) );
}

// Display User Field @ Order Meta

add_action( 'woocommerce_admin_order_data_after_billing_address', 'bbloomer_checkout_field_display_admin_order_meta', 10, 1 );

function bbloomer_checkout_field_display_admin_order_meta( $order ) {    
echo '<p><strong>'.__('Acu #').':</strong> ' . get_post_meta( $order->id, '_student_acu_no', true ) . '</p>';
}

Where to add this code?

You can place PHP snippets at the bottom of your child theme functions.php file (before "?>" if you have it). CSS, on the other hand, goes in your child theme style.css file. Make sure you know what you are doing when editing such delicate files - if you need more guidance, please take a look at my free WooCommerce Customization video tutorial.

Does this snippet still work?

Please let me know in the comments if everything worked as expected. I would be happy to revise the snippet if you report otherwise (please provide screenshots). I have tested this code with Storefront theme, the WooCommerce version listed above and a WordPress-friendly hosting on PHP 7.

If you think this code saved you time & money, please join other Business Bloomer supporters and avail of 365 days of WooCommerce benefits. Thank you in advance :)

Need Help with WooCommerce?

Check out these free videos, tutorials and tips!

  • how-to-edit-woocommerce-with-php-snippets
  • woocommerce-hooks-add_action-list-visual
  • woocommerce-customize-single-product-page-PHP
  • woocommerce-customize-shop-page-PHP
  • woocommerce-advanced-customization
  • how-to-edit-woocommerce-cart
  • woocommerce-customize-checkout-page-PHP
  • woocommerce-email-customization
  • woocommerce-conditional-logic

Rodolfo Melogli

Author, WooCommerce expert, WordCamp speaker and Internet marketer, Rodolfo Melogli has worked as a WooCommerce freelancer since 2011. He helps entrepreneurs and developers overcome their WooCommerce nightmares :) Rodolfo is the organiser of WordCamp Dublin, the Dublin WooCommerce Meetup, the Dublin Ecommerce Meetup and the Dublin WordPress Meetup. He enjoys interacting with people, travelling and chasing tennis & soccer balls. Of course, he loves pizza too.

35 thoughts on “WooCommerce: How to Add a Custom Checkout Field (PHP)

  1. Hello Rodolfo. Thank you very much for your tutorials. I’ve used several!

    About this in particular: How could I turn the field into a dropdown (that needs to be saved and retrieved multiple times)

    Very obliged if you can help!

    1. Hey Marcio, thanks so much for your comment! Yes, this is possible – but unfortunately this is custom work and I cannot provide a complementary solution here via the blog comments. Thanks a lot for your understanding! ~R

  2. In Part 5 of 5, you reused the function name from updating user_meta to update post_meta.

    add_action('woocommerce_checkout_update_order_meta', 'bbloomer_custom_checkout_field_update_user_meta');

    This conflicts with the code from Part 3 of 5.

    It think this fixes the issue:

    // Update order meta with field value
     
    add_action('woocommerce_checkout_update_order_meta', 'bbloomer_custom_checkout_field_update_order_meta');
     
    function bbloomer_custom_checkout_field_update_order_meta( $order_id ) { 
    if ($_POST['student_acu_no']) update_post_meta( $order_id, '_student_acu_no', sanitize_text_field($_POST['student_acu_no']) );
    }
     
    // Display User Field @ Order Meta
     
    add_action( 'woocommerce_admin_order_data_after_billing_address', 'bbloomer_checkout_field_display_admin_order_meta', 10, 1 );
     
    function bbloomer_checkout_field_display_admin_order_meta($order){    
    echo '<p><strong>'.__('Acu #').':</strong> ' . get_post_meta( $order->id, '_student_acu_no', true ) . '</p>';
    }
    
    1. Hey JB, thanks so much for that. Sure there was a conflict?

  3. Hi Rodolfo,

    Thanks for all the tutorials you have provided for free.

    Can you please tell me how to display a custom field created for checkout page on any other page.

    e.g. I want to display a custom field in my invoice.

    1. Hey Omkar, thanks so much for your comment! Yes, this is possible – but unfortunately this is custom work and I cannot provide a complementary solution here via the blog comments. Thanks a lot for your understanding! ~R

  4. Please how do I override the default :
    do_action( ‘woocommerce_review_order_after_order_total’ );

    in the checkout page. Also where is the location of the default implementation.

    Thanks in advance for your support

    1. Hello Webb, thanks so much for your comment! Yes, this is possible – but unfortunately this is custom work and I cannot provide a complementary solution here via the blog comments. Thanks a lot for your understanding! ~R

  5. Hi, Rodolfo!

    Thank you so much for this guide.
    Is there a way to change order of this fields?

    I would like to make a field ‘Company ID number’ (this information is required in Thailand), and place it after ‘Company name’ field.

    Currenly for re-orderind standart fields using this snippet:

    function bbloomer_move_checkout_fields_woo_3( $fields ) {
      $fields['first_name']['priority'] = 95;
      $fields['last_name']['priority'] = 96;
        // 100 110 phone & e-mail
      $fields['company']['priority'] = 130;
      $fields['country']['priority'] = 140;    
      $fields['address_1']['priority'] = 150;
      $fields['address_2']['priority'] = 160;
      $fields['city']['priority'] = 170;
      $fields['state']['priority'] = 180;
      $fields['postcode']['priority'] = 190;
        return $fields;
    }
    

    Woo version 3.0 +

    Thank you!

    1. Tim, thanks for this – I see you’re referencing my snippet from https://businessbloomer.com/woocommerce-move-reorder-fields-checkout-page/. What have you tried so far with your custom field?

    2. Rodolfo, exactly! For ordering I am also using your snippet.

      I’ve tried

      $fields['custom_field']['priority'] = 101;
      

      But that method isn’t working for custom field

    3. Uhm, I see, sorry to hear that. Unfortunately I can’t spend too much time on custom support here in the blog 🙂

    4. What action or filter to we hook this to?

  6. Hi,
    Great site and tutorials!
    If I only want to have a field so my customer could sign there personal number, and it must be optional.
    How do I do that?

    Thank´s

    1. I´m sorry!
      I ment it must be mandatory. not optional.

    2. Hey Greger! The line:

      'required'   => true,
      

      is already telling Woo to make the field mandatory 🙂

  7. hi, Rodolfo.
    First of all – thank you for this amazing site. It has so much useful information, it’s unbelievable.
    Now, could i use this code to only let the admin put in a number? if so, how?
    I would need a field where i as admin could enter a specific document number to that field and it should be visible in order and email, but only for one order. On the next order of any client i would then put another number in.
    And is there a way to change the price on checkout by admin too?

    Thank you for everything you do so we who are just starting can have it so much easier.

    1. Hey Mark, thanks so much for your comment! You should use https://codex.wordpress.org/Function_Reference/is_admin to conditionally show the field. Also, changing the price at checkout is possible, but very custom and unfortunately I can’t offer a complementary fix here on the blog. Thanks for your understanding 🙂

  8. Great tutorial, thank you Rodolfo.

    I have followed it closely and tested after adding each snippet, and it’s working perfectly.

    Except, the last snippet. The field name shows on the order page, eg: Acu#, but the value doesn’t show.

    I have checked my code against yours and in the woocommerce docs.

    Any idea why this won’t work?

    Thank you

    1. Hey Jason, thanks for your comment! The only thing I can think of without testing is the “_student_acu_no” bit in snippet #5. Try to remove the initial underscore “_” from there and see if it works 🙂 Let me know!

    2. Hi Rodolfo, I did try that and it didn’t work.

      I also tried update_post_meta, like in snippet 3, to save the data to the post meta database. Still no luck.

    3. Uhm, weird. Luckily, today I’m working with a client on this exact same task, so I will let you know if it works (and if not, remind me in a couple of days). R

    4. To anyone else having this problem. You need to update you need to change the “esc_attr” in “esc_attr($_POST[‘student_acu_no’])” with “sanitize_text_field”. So it becomes “sanitize_text_field($_POST[‘student_acu_no’])”

      This worked for me to get it to show up in backend.

      Btw, great article Rodolfo. I love your site.

  9. Hey Rodolfo,

    Great site and tutorials! I have a site using woocommerce that needs something very similar.. was hoping you might point me in the right direction.. I need to add a select list to checkout, for an employee to choose their regional director from.. the confirmation email has to be sent to the regional director so they can login and change the order status.. showing their approval or denial of the order.

    What is the best way to:
    1) Add a select list for a customer to choose a value from (in this case their Regional Director, whose email would be the value we need)
    2) Add that email to the admin order confirmation email

    Thank you for any help you can provide! Also, are you available for small projects like this one I just asked about above? I’m a UX Engineer and have a blast creating engaging sites, and hand coding the front end, some backend, etc. But, it would be great to have someone with your skill set for these tasks that are out of my area of expertise! Let me know and thanks again.

    1. Jesse, thanks so much for your comment! Yes, this is possible – but unfortunately this is custom work and I cannot provide a complementary solution here on the blog.

      Indeed, I provide WooCommerce support and customization on an hourly basis. If you’d like to get a quote, feel free to contact me here.

      Thanks a lot for your understanding!

      ~R

  10. Hi Rodolfo, I would like to automate the address fields in this way,

    When we select a ‘country’, it will activate the ‘province’ field with select option below the country field.
    Now when we select a ‘province’, it will activate another field ‘city’ with the same select option below the ‘province’ field.
    And finally when we select a city, it will show the ‘address’ field where we can write the house/street no etc.

    I’ve googled but yet couldn’t find a solution for this anywhere. I hope soon I will hear from you. Thanks in advance 🙂

    1. Hello Mahir, thanks for your comment! And nice one by the way – unfortunately this needs to be custom coded and I can’t offer a free solution here on the blog. I would recommend, however, to check this tutorial to reorder fields on the checkout: https://businessbloomer.com/woocommerce-move-reorder-fields-checkout-page/, and then after that you’d need to add 2 new fields (city & province dropdowns). You can follow in this case this tutorial that does a similar thing (not a dropdown though): https://businessbloomer.com/woocommerce-add-house-number-field-checkout/. Hope this helps 🙂

  11. hi! is it possible to make it visible in multiple categories (online-courses, books)?

    1. Hey Filippo, thanks for your comment! Yes, you can just add another function like this:

      
      function bbloomer_is_category_19_in_cart( $product_id ){
      // ID 19 = other cat
      return has_term( 19, 'product_cat', get_post( $product_id ) );
      }
      
      

      And then add another check in the existing function:

      
      if( bbloomer_is_category_18_in_cart( $product_id ) || bbloomer_is_category_19_in_cart( $product_id ) ){
      
      

      Let me know 🙂

  12. Thank you very much for this useful tutorial and code.

    However, If I want to add options to the address field instead of text box, how can that be done.

    for example I only ship to three areas in one city and I want them to be able to choose from these areas under the Address, instead of entering their address manually.

    If you have an existing tutorial, i would appreciate your guidance to it.

    Thank you

    1. Great question Sara, thanks! You’re basically looking for a way to limit shipping to certain “default” areas e.g. “New York City 10001” or “New York City 10009”, aren’t you? If yes, does every area have a specific street address, zip code, city, state and country?

  13. Great Rodolfo,

    Nice, flexible options will try definitely

    1. Thank you so much Lubo!

Questions? Feedback? Support? Leave your Comment Now!
If you're writing code, please wrap it between: [php] code_here [/php]

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.