This post is meant as a one stop shop if you’d like to make any kind of customizations to your WooCommerce checkout fields. Whether this is adding additional fields, removing some unneeded ones or changing the order they’re displayed in.
Additionally there will be guides on how do display fields two field side by side, updates the order totals when a field changes and how to add basic field validation.
This is a post with a lot of code snippets and likely requires changes for it to fit your exact needs. Prefer to use a plugin instead? Take a look at my Advanced Checkout Fields for WooCommerce plugin.
Good to Know
These are some good to know files, hooks and functions/methods. Some of these will be used throughout the post, others are related and may be of use for your specific use case.
Files/Functions
includes/class-wc-countries.php
get_default_address_fields()
get_address_fields( $country = '', $type = 'billing_' )
includes/class-wc-checkout.php
get_checkout_fields( $fieldset = '' )
includes/wc-template-functions.php
woocommerce_form_field( $key, $args, $value = null )
Hooks
There are different ways (filters) you can modify checkout fields in WooCommerce. The following hooks can be used accordingly to their use case.
woocommerce_default_address_fields can be used when you’d like to add a address field for all countries.
The woocommerce_billing_fields/woocommerce_shipping_fields hooks are also address only fields, so be aware that these omit the ‘Company’ field for example and is also not the best place to add product fields for example.
woocommerce_checkout_fields is the last in line, this is the very last filter that can be called and contains all the actual checkout fields that will be displayed to the customer.
Checkout Field IDs
When managing the checkout fields you’ll require the checkout field IDs accordingly. Here’s a full list of all the field IDs accordingly.
Billing Fields
- billing_first_name
- billing_last_name
- billing_company
- billing_country
- billing_address_1
- billing_address_2
- billing_city
- billing_state
- billing_postcode
- billing_phone
- billing_email
Shipping Fields
- shipping_first_name
- shipping_last_name
- shipping_company
- shipping_country
- shipping_address_1
- shipping_address_2
- shipping_city
- shipping_state
- shipping_postcode
Account Fields
- account_username
- account_password
Order Fields
- order_comments
Adding Checkout Fields
The following example shows the addition of a basic billing field – using the woocommerce_billing_fields
hook.
/**
* 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' );
This example adds a very simple text field to the billing fields. There are many additional options you can pass within the array to customize your checkout field. Below is the entire list of the default attributes (and values) that you can pass and override accordingly.
One important thing to note is that the field ID should be prefixed with billing_
, shipping_
, account_
or order_
according to where you’re adding the field to. This ensures the field is automatically processed correctly within WooCommerce Core without additional effort needed.
$defaults = array(
'type' => 'text',
'label' => '',
'description' => '',
'placeholder' => '',
'maxlength' => false,
'required' => false,
'autocomplete' => false,
'id' => $key,
'class' => array(),
'label_class' => array(),
'input_class' => array(),
'return' => false,
'options' => array(),
'custom_attributes' => array(),
'validate' => array(),
'default' => '',
'autofocus' => '',
'priority' => '',
);
That’s are quite a few arguments! but luckily you don’t need to touch most of them as shown with the basic example. Some of the arguments deserve some additional attention;
Type
The type
field is the one that determines what type of field is used. WooCommerce by default has 18 registered field types. Later in this post more about the different checkout field types.
Description
The description
argument adds a description to the front-end and will only be visible when the customer is focussed on the specific field.
Priority
The priority
argument is what determines the order in which the checkout fields will appear. All fields are sorted automatically going from low to high, making it easy to determine the position of a field instead of having to have it at a specific index/position within a list/array.
I’m touching more on sorting checkout fields later.
Validate
The validate
argument is meant to allow for adding additional validity check(s) to the field. Validation checks are done at the checkout when trying to create the order – so only after the customer presses the “Place order” button.
Default available options are; phone
, email
, state
and postcode
. Later in this post I’ll show you can Create a custom checkout field validation.
Checkout Field Types
There are 18 different field types you can choose from by default. I’ll list them all below, some are for more generic use and others are more for one time use such as ‘Country’ or ‘State’, but if wanted you can use them multiple times.
- country
- state
- textarea
- checkbox
- text
- password
- datetime
- datetime-local
- date
- month
- time
- week
- number
- url
- tel
- select
- radio
Creating a Custom Checkout Field Type
Is the field you’re looking for not available in the default list? It is possible to create your own custom field type and use it accordingly. Here’s a quick example of creating a ‘description’ field type that simply adds a description text as a field to the checkout page.
/**
* Simple example of a custom WC checkout field.
*/
function js_description_wc_checkout_field( $field, $key, $args, $value ) {
$sort = $args['priority'] ? $args['priority'] : '';
$container_class = esc_attr( implode( ' ', $args['class'] ) );
$container_id = esc_attr( $args['id'] ) . '_field';
ob_start();
?><p class="form-row <?php echo $container_class; ?>" id="<?php echo $container_id; ?>" data-priority="<?php echo esc_attr( $sort ); ?>">
<?php echo wp_kses_post( $args['label'] ); ?>
</p><?php
return ob_get_clean();
}
add_filter( 'woocommerce_form_field_js_description', 'js_description_wc_checkout_field', 10, 4 );
Since ‘description’ is quite general I’ve prefixed it with js_
to ensure the chances of it conflicting is close to zero. With this code in place you can now add your custom field using the js_description
in the type
parameter. It shows the label
argument as the description. Of course you can modify this to output any custom HTML field you want/need.
Take a look at the woocommerce_form_field()
function to see how the default fields are formatted and structured. This shows a lot of what is expected and how you can create your own custom field type.
Update Totals on Field Change
By default WooCommerce will update the order totals table automatically when the required address fields are all filled out and when any of those fields change. Updating the order totals table when your custom field changes is very easy..
When adding your custom field, simply add a update_totals_on_change
to the class
attribute and it will automagically update the totals whenever the field value changes.
/**
* Update totals
*/
function jeroensormani_add_checkout_fields( $fields ) {
$fields['billing_FIELD_ID'] = array(
'label' => __( 'FIELD LABEL' ),
'type' => 'text',
'class' => array( 'form-row-wide', 'update_totals_on_change' ),
'priority' => 35,
'required' => true,
);
return $fields;
}
add_filter( 'woocommerce_billing_fields', 'jeroensormani_add_checkout_fields' );
Adding Checkout Fields in the Admin
The admin interface does not show the newly added field automatically. There are additional filters where your custom field(s) need to be added to. The following are available for the shipping/billing fields;
woocommerce_admin_billing_fields
woocommerce_admin_shipping_fields
Adding your custom field here is easier then adding it to the checkout, you only have to pass the field ID and the label – most of the time. The following adds the newly added checkout field to the admin.
function js_woocommerce_admin_billing_fields( $fields ) {
$fields['FIELD_ID'] = array(
'label' => __( 'FIELD LABEL' ),
'show' => true,
);
return $fields;
}
add_filter( 'woocommerce_admin_billing_fields', 'js_woocommerce_admin_billing_fields' );
Take note that the FIELD_ID
must be without the billing_ / shipping_
prefix as WooCommerce adds this automatically.
There are additional attributes you can pass to the array. You can already see I passed show => true
in the example. Passing that attribute => value ensures the field and value are shown similar to the Email/Phone fields on the order view.
The types of editing fields are a bit more limited then the checkout fields, you can only choose to make it a text field (default) or set the 'type' => 'select'
to make the edit field a dropdown.
Removing Checkout Fields
Removing any checkout field is quite easy. The only thing to keep in mind is that the ‘Country’ field is a non-removable field as WooCommerce Core does a hard check to see if its available. If you do remove it the order cannot be completed and will give an error saying “Please enter an address to continue.”.
Now – I can’t think of any reason why any store would need this, but the following code removes ALL checkout fields that can be removed, and Woo will still work as expected. You can however easily use this snippet and simply eliminate the code that you don’t want.
/**
* Remove all possible fields - example.
*/
function js_remove_checkout_fields( $fields ) {
// Billing fields
unset( $fields['billing']['billing_company'] );
unset( $fields['billing']['billing_email'] );
unset( $fields['billing']['billing_phone'] );
unset( $fields['billing']['billing_state'] );
unset( $fields['billing']['billing_first_name'] );
unset( $fields['billing']['billing_last_name'] );
unset( $fields['billing']['billing_address_1'] );
unset( $fields['billing']['billing_address_2'] );
unset( $fields['billing']['billing_city'] );
unset( $fields['billing']['billing_postcode'] );
// Shipping fields
unset( $fields['shipping']['shipping_company'] );
unset( $fields['shipping']['shipping_phone'] );
unset( $fields['shipping']['shipping_state'] );
unset( $fields['shipping']['shipping_first_name'] );
unset( $fields['shipping']['shipping_last_name'] );
unset( $fields['shipping']['shipping_address_1'] );
unset( $fields['shipping']['shipping_address_2'] );
unset( $fields['shipping']['shipping_city'] );
unset( $fields['shipping']['shipping_postcode'] );
// Order fields
unset( $fields['order']['order_comments'] );
return $fields;
}
add_filter( 'woocommerce_checkout_fields', 'js_remove_checkout_fields' );
Sorting Checkout Fields
Positioning checkout fields is luckily very easy – mostly. WooCommerce Core does all the heavy lifting in term of actually sorting them, you just provide have to provide the correct priority to the field(s).
Checkout Field Default Priorities
In order to be able to position your field at the right location you of course need the priority of existing fields. This is the full list of all default fields with their priorities;
- 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
Note: For many countries the priority for the Postcode / ZIP field is changed to 65 by WooCommerce.
Repositioning Existing Checkout Fields
You’ve already seen how to provide the priority
attribute when adding a new checkout field, to re-position a existing field you can do something like below to accomplish that.
/**
* Reposition email/phone fields.
*/
function js_sort_checkout_fields( $fields ) {
$fields['billing']['billing_email']['priority'] = 22;
$fields['billing']['billing_phone']['priority'] = 23;
return $fields;
}
add_filter( 'woocommerce_checkout_fields', 'js_sort_checkout_fields' );
Aligning checkout fields
By default the first- and last name fields are the only two fields that are aligned side by side and the rest of the fields are full width. Aligning the checkout fields can be done by adding one of the below strings to the class
attribute when adding a field.
form-row-first
form-row-last
form-row-wide
The first and last name fields have the form-row-first
and form-row-last
values accordingly to show them side by side. This is an example that gives the same treatment to the two address fields to show them side by side.
function js_align_address_checkout_fields( $fields ) {
if ( ( $key = array_search( 'form-row-wide', $fields['billing_address_1']['class'] ) ) !== false ) {
unset( $fields['billing_address_1']['class'][ $key ] );
}
if ( ( $key = array_search( 'form-row-wide', $fields['billing_address_2']['class'] ) ) !== false ) {
unset( $fields['billing_address_2']['class'][ $key ] );
}
$fields['billing_address_1']['class'][] = 'form-row-first';
$fields['billing_address_2']['class'][] = 'form-row-last';
return $fields;
}
add_filter( 'woocommerce_billing_fields', 'js_align_address_checkout_fields' );
Custom Checkout Field Validation
I’ve touched on checkout field validation shortly before showing the WooCommerce Core validation options. Now I’d like to show a custom validation rule. In my example below I added a validation that checks for a valid 10-alphanumeric customer club number.
/**
* Add custom field validation
*/
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( 'club-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 );
Here’s a corresponding checkout field snippet that adds the ‘Club number’ field with its validation accordingly.
**
* Add Club number field.
*/
function jeroensormani_add_checkout_fields( $fields ) {
$fields['billing_club_number'] = array(
'type' => 'text',
'label' => __( 'Club number' ),
'description' => __( 'Please enter your club number found on your club card' ),
'priority' => 35,
'validate' => array( 'club-number' ),
);
return $fields;
}
add_filter( 'woocommerce_billing_fields', 'jeroensormani_add_checkout_fields' );
69 thoughts on “Ultimate Guide to WooCommerce Checkout Fields”
Hello Jeroen,
Very interesting and potentially useful. We’re looking for a way to send emails to addresses specified in the custom fields, because the purchaser may be buying products for other people. Is there a function or hook that can do this?
Hi Chris,
It is possible to send the customer emails to a custom email address. The recipient would need to be changed in the order emails using the ‘woocommerce_email_recipient_{EMAIL_ID}’ hook.
Hi,
I created 2 custom billing fields. Your explanations helped a lot.
Now the code I wrote work in user edit billing address page, emails, checkout page, orders admin panel.
The things I can’t achieve as I like:
1. my regex to check 1 of the fields (company ID) doesn’t work as expected. The fields has to be 9 or 10 digits only. The current regex is: “/^[1-9]?[0-9]{9,10}/”,it doesn’t accept 10 symbols. I can’t find where I’m wrong. If you need the code – I’ll share it.
2. I want to see these fields in billing section in my admin panel in user edit/create user pages. I can’t figure it out how to do it.
I’ll be very thankful and very happy to help with these issues.
I hope to receive your guidance!
Thank you!
Hi Jeroen,
thank you so much for this detailed tutorial!
I have a little problem with the `billing_address_1` and `billing_address_2` fields because I am unable to position them even if I set a lower priority.
Here’s my code if you want to check it out: https://gist.github.com/AndreaBarghigiani/ed3b47c3a4da2d26e3150bf62db1c30b
The strange thing is that at first load I see both fields above the name ones but after a second it gets moved back to their original position. I’ve tested also with the Twenty Ninetheen and with all plugins deactivated but I still have the same problem.
If I try to reorder any other element I have no problem at all. At this point my only solution is to move the other elements below the billing addresses but it seems wired that we cannot select those field.
Thank you again for the article and your help.
Hi Andrea,
I believe this could be due to some prioritizing/sorting thats done through JS. I don’t have the solution at the top of my head, but I believe it can be solved by hooking into another filter and setting priority there as needed.
Cheers,
Jeroen
Hi Jeroen,
recently I have encountered a problem with the check out section of my website. There are two billing and paying section instead of one on my check out page. It’s like everything is duplicated. I have done the plugin/theme conflict test and it didn’t solve the problem. I also tried to removed the fields but it removes the fields from both billing sections. I have no idea how to fix this problem and would appreciate your help.
Thanks
Hi Farnaz,
Unfortunately there’s no easy answer to something like this; its likely something in your theme / a plugin that is causing this.
A developer should be able to assist debugging/figuring this out on your site.
Cheers,
Jeroen
Hi Jeroen, thanks for your great article. I used the align checkout field snippet and it worked great. Now I am trying to simply renamen the default fields billing_address_1 and shipping_address_1 to something like Street & Housenumber. However my code does not seem to work, since at checkout the field keeps as “Street” only:
// Billing_Adress 1 field will be shown as Straße + Hausnummer instead of just Straße
function rename_billing_address_1_field( $fields ) {
$fields[‘billing’][‘billing_address_1’][‘label’] = ‘Straße & Hausnummer’;
$fields[‘billing’][‘billing_address_1’][‘placeholder’] = ‘Straße & Hausnummer’;
return $fields;
}
add_filter( ‘woocommerce_checkout_fields’ , ‘rename_billing_address_1_field’ );
Maybe you have an idea how to make that work. I am sure a few other reades might have the same problem and benefit as well.
Thanks a lot!
Tim
Hi Tim,
I believe you’d need to use a different filter for changing that one; I believe its the default filter that should work with this.
Cheers,
Jeroen
Hello, great post. I have a question: I removed the last name field and now the first name is aligned to the left side and it doesn’t look good, How can I get this field to show full width?
Thank you!
Hi Mauro,
You’d need to set the following array item;
'class' => array( 'form-row-first' )
Cheers,
Jeroen
Hi Jeroen! thanks for this guide. My question is why wooCommerce doesn’t allow to add dynamic options into a select field in the checkout moment?
Regards.
Hi David,
This is because the checkout fields do not update when changing a field. This would require additional work for it to function. The https://aceplugins.com/plugin/advanced-checkout-fields-woocommerce/ plugins can change fields (not values of a field though) based on other field values.
Cheers,
Jeroen
hi, Jeroen Sormani
very nice guide.
The email field is mandatory in the billing checkout page. I want to convert it to optional. how to do it ?
Hi Yasir,
You’d need to modify the ‘required’ parameter to false to make it optional. From the top of my head I’m not sure if Woo requires further customization to make the email field optional.
Cheers,
Jeroen
Hi Jeroen,
I want to create a select-type field Consumer | Company and hide the company field for consumers. How do I define the select options an the if statement?
Regards,
Arjan
Hi Arjan,
Conditional fields like that would require further work.
The Advanced Checkout Fields plugin has this build in though and can be used out of the box to show/hide field based on the selected value of another field.
Cheers,
Jeroen
Hi Jeroen, great tutorial!
I wonder if you could help me out with this issue:
How can I show the label name before billing addrress items in billing details section for thank you page and mails too?
I’m looking for long time but nothing found around.
Regards,
Roberto
Hi Roberto,
Woo displays the shipping/billing details on the thank you page / emails as one piece in a address format, not as separate fields. If you’d like something like this a customization to display the fields separately would be needed.
Cheers,
Jeroen
Thankyou Jeroen for your fast replay. I’m following you…
Hi
Thank you for your comprehensive article.
I have a question about woocommerce checkout.
I use the below code to fill email address (or ant other field) with a default value:
add_filter( ‘woocommerce_checkout_fields’ , ‘default_values_checkout_fields’ );
function default_values_checkout_fields( $fields ) {
// You can use this for postcode, address, company, first name, last name and such.
$fields[‘billing’][‘billing_email’][‘default’] = ‘example@email.com’;
return $fields;
}
But I want to do this if the user leaves the email field empty.
Is there any way to do this.
Hi Amin,
Using a default when left empty is probably something that should be done after the order has been submitted and not something to modify within the checkout fields (though you’d need to set it to not be required).
Cheers,
Jeroen
Hi, I get the data that customers enter in the form without the respective label
For example, if a customer fills the order in this way:
NAME: gino
CITY: rome
MAIL: example@example.ex
I receive:
gino
rome
example@example.ex
-Can you help me to understand how to show the label of the inserted data please? In this way in the page of modification of the data of the customer, and above all in the mails that you receive after the order will be shown the data and the respective labels:
NAME: gino
CITY: rome
MAIL: example@example.ex
Sorry, I use a translator 🙂
Thanks for your attention and many compliments for your very interesting site!
Hi Gino,
For this you’d want to look into modifying the address format, this is not directly related to modifying the checkout fields themselves.
Cheers,
Jeroen
Nice article! I would like to understand how to create a new checkout field that updates when, for example, the shipping address is updated. For example, I would like a calendar for the shipping date selection that is updated (the date range) according to the postal code. Do I have to manage it myself with javascript or is it possible to update the field through the ajax call update_order_review? Thanks!!
Hi,
Checkout fields in Woo are not updated out of the box once the page had finished loading. It is possible to build something like this, but as you thought this would have to be done manually w/ JS.
Cheers,
Jeroen
I am curious about your checkout field validation method. I’m no expert, but I added your code to include in the checkout (showing as club member currently). What I am seeking is a way to validate the entered value in the checkout field against a user profile field. In this way, if the entered value at checkout matches exactly a value in a field in the user profile they are allowed to proceed checkout.
Also, is there any way to not include this field in the customers billing account details.
Hi,
Something like that would require a custom validation function, I don’t believe WooCommerce has something out of the box that can do this. If you need assistance on this I’d be happy to talk further.
Cheers,
Jeroen
Hi Jeroen,
Thank you for your article. I have a question on how I would be able to access the billing address and delivery address before they place an order. I need to encrypt this data before inserting into the database. I know how to encrypt but wondered if this is possible for me to access the data before it gets inserted into the database when they place an order.
I know that the address maybe put into different table names for example: wp_postmeta or wp_usermeta but just wondered if this was possible?
Thank you
Tony
Hi Tony,
WooCommerce is pretty flexible in accessing the billing/shipping information and modifying the data before/as it is stored. Maybe the ‘woocommerce_create_order’ hook works for you, or if needed you can even go earlier than that..
Cheers,
Jeroen
Hi Jeroen,
I am triying to add a new delivery address how can i do that wihtout use the fields from billing, shipping or note. I mean there is a way to add new individual fields?
I want 3 checkboxs 1 for current address, 2 for shipping and 3 for another address i already have the first two but do not now how to add the third!
Regards,
Jean
Hi Jean,
It is possible to do, but WooCommerce does not have a build-in way of doing this like you can do with adding other fields in the existing field types (shipping, billing, other).
Cheers,
Jeroen
Hello Jeroen,
Thanks for this Blog! I was able to change the group field from “account_password” to the billing section. It works! However, there is a checkbox that activates the password field in order that a customer can create a account via checkout process. Now, after adding the account_password field to the billing group, the checkbox is still in the old section from account. Do you know a way to also change the checkbox group?
function move_password_field($checkout_fields){
// Move Account Password into Billing
$checkout_fields[‘billing’][‘account_password’] = $checkout_fields[‘account’][‘account_password’];
// Remove Password from Billing
unset($checkout_fields[‘account’][‘account_password’]);
return $checkout_fields;
}
add_filter(‘woocommerce_checkout_fields’, ‘move_password_field’, 999);
Hi Nik,
You could disable the checkbox by adding;
add_filter( 'woocommerce_checkout_registration_required', '__return_false' );
You’d want to make sure the registration still works, as I do think Woo checks if the checkbox (field) is set. If so, you may want to add a hidden field instead.
Cheers,
Jeroen
Hello Jeroen,
Thanks for your reply.
We actually want to keep the checkbox. So a customer is able to select if he want to create a account or not. But if the customer want to create a account, we want to have the password field right after the email field. For that we use our snipped above which we created with the inputs from your blog post. 🙂
Do you see a way how we can do that?
Cheers
Hi Nik,
You’d want to use the previous code to remove the existing checkbox, and then re-create it at the position you’d want it to (below email field). Make sure to use the same field name for it.
Cheers,
Jeroen
Not sure how we can do that really. Can we hire you for that little task? 🙂
Feel free to reach out if you’d like to discuss this 🙂
Cheers,
Jeroen