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        Get CustomizeWoo.com FREE
 * @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 snippet?

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 files - if you need more guidance, please take a look at my free video tutorial "Where to Place WooCommerce Customization?"

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 Customization?

Check out these free video tutorials. You can start learning how to customize WooCommerce without unnecessary plugins. Watch me code and learn by example!

  • how-to-edit-woocommerce-with-php-snippets
  • woocommerce-hooks-add_action-list-visual
  • woocommerce-customize-single-product-page-PHP

Rodolfo Melogli

Author, WooCommerce expert and WordCamp speaker, Rodolfo has worked as a WooCommerce freelancer since 2011. His goal is to help entrepreneurs and developers overcome their WooCommerce nightmares. Rodolfo loves travelling, chasing tennis & soccer balls and, of course, wood fired oven pizza.

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

  1. Hi Rodolfo

    Thanks for this great site, you’re a legend ๐Ÿ™‚

    I appear to be having similar issues to the most recent comments: part 5, get_post_meta() appears to return an empty value. update_post_meta() works because I can see the custom field and value in the order dashboard. I just can’t seem to ‘extract’ the value and display it. Any ideas?

    1. Hi David, where do you need to display it?

      1. I want to display the custom field on the ‘thank you’ page. Interestingly, get_post_meta() works when applied to woocommerce_admin_order_data_after_billing_address but it returns nothing when applied to woocommerce_thankyou. I’m beginning to wonder if maybe the custom field is stored in the database after the ‘thank you’ page has been displayed, so the data it’s trying to access isn’t there yet. Really weird!

        1. No David, the thank you page is not too early. There must be something else ๐Ÿ™‚ Code you used?

  2. Hello Sir Rodolfo, thank you very much for valuable tutorial. I followed the syntax on part 5 of 5, but I think update_post_meta doesn’t save to database, the custom field returns blank data on order/thank you page, please help.

    1. Did you change the code? If yes paste it here and I’ll see if I can spot the error

  3. Hi, thanks for the information. I follow the instructions, but the new fields are shown blank on the preview popup in the order page. You have to enter into the order details to read these new fields.

    1. Hello Greg, did you edit my code? I’m afraid it’s custom work. If you’d like to get a quote, feel free to contact me here. Thanks a lot for your understanding!

  4. Hey, I’m trying to add custom checkout fields if a product attribute be in the cart. I wrote the code based on shipping method, but couldn’t find anything for making it work with product attribute change. can you help me please. is it possible or not? I need it a lot. thanks ๐Ÿ™‚

    1. Hello Somy, thanks so much for your comment! Yes, this is possible – unfortunately this is custom work and I cannot provide a complementary solution here via the blog comments. If you’d like to get a quote, feel free to contact me here. Thanks a lot for your understanding! ~R

  5. I tried your code and it worked well. However, the field is marked as required (with a red star next to it, but the checkout form gets submitted even when it’s blank. Any clue why?

    Thanks so much. I really enjoy your site. It’s so helpful.

    1. Hey Rocheli, thanks for your comment! You’re missing another part of the snippet where validation is done… check my tutorial again ๐Ÿ™‚

  6. Very nice!
    There is just one thing that upsets me in the last step 5 out of 5.
    Isn’t there an alternative way to add it directly to the arrays of fields and not inserting HTML?
    Possibly making it also editable admin side for manual order updates.

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

  7. Hello Rodolfo,
    Thank you so much again amazing tutorial. I just searching custom conditional field by user role at checkout page. It seems so simple but i could not handle it. I have a custom role which same as customer (calling WHOLESALER) but this role just having COMPANY.
    So i want to make before checkout billing field a radio field with two options ” is personal or company”
    When customer check personal then display default checkout’s fields except COMPANY NAME.
    When customer check company then display default fields + Company Tax Number and Company Tax Name.
    And the fields (company tax name and tax number and company name ) should visible WHOLESALER’s account’s page ( it should not be editeble)
    And the fields should automatically filling if WHOLESALER sign in their login information.
    Could you please help me for this or directive how can do that?

    1. Hello Ahmet, 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. If you’d like to get a quote, feel free to contact me here. Thanks a lot for your understanding! ~R

  8. 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

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

  10. 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

  11. 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

  12. 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

      1. Uhm, I see, sorry to hear that. Unfortunately I can’t spend too much time on custom support here in the blog ๐Ÿ™‚

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

  13. 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.

      1. Hey Greger! The line:

        'required'   => true,
        

        is already telling Woo to make the field mandatory ๐Ÿ™‚

  14. 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 ๐Ÿ™‚

  15. 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!

      1. 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.

        1. 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

    2. 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.

  16. 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

  17. 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 ๐Ÿ™‚

  18. 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 ๐Ÿ™‚

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

  20. Great Rodolfo,

    Nice, flexible options will try definitely

    1. Thank you so much Lubo!

Questions? Feedback? Support? Leave your Comment Now!
_____

If you are writing code, please wrap it between: [php]code_here[/php]. Failure to complying with this (as well as going off topic) will result in comment deletion. You should expect a reply in about a week - this is a popular blog but I need to get paid work done first. Please consider joining #BloomerArmada to ask me 1-to-1 WooCommerce questions. Thank you :)

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