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.

‘Description’ argument example

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.

  1. country
  2. state
  3. textarea
  4. checkbox
  5. text
  6. password
  7. datetime
  8. datetime-local
  9. date
  10. month
  11. time
  12. week
  13. number
  14. email
  15. url
  16. tel
  17. select
  18. 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.

  • ‘show’ => true
  • Editing billing fields

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' );

Jeroen Sormani

I'm a professional WordPress plugin developer on a mission to create the best plugins for my clients. I'm specialised in developing general WordPress, WooCommerce and Easy Digital Downloads plugins.

Interested in talking about a WordPress project? Get in touch!

Follow me on Twitter

69 thoughts on “Ultimate Guide to WooCommerce Checkout Fields

Matthieu August 26, 2018 at 6:07 pm

Hello Jeroen,

Many thanks for this great guide. Is there any way to mark the newly added field red when the input is invalid? So the customer immediately notices which field needs to be adjusted. Many thank in advance!

Best,

Matthieu

Jeroen Sormani August 27, 2018 at 9:30 am

Hi Matthieu,

Something like that is possible – I’d recommend adding a specific class to the new field and create a custom JavaScript function that does the validation accordingly. WooCommerce does not have a out-of-the-box solution for that though.

– Jeroen

David September 27, 2018 at 2:47 am

Jeroen, third time that you saved my life. Thanks again and again for such valuable post.

Attila October 19, 2018 at 11:00 pm

Hi Jeroen,
Thank you it is a great tutorial.
I am trying to use your custom validation function on a not required field. So the field is optional but if the user enter something I validate that. Basically it works well the error message appears above the checkout fields, but the input field remains valid (green). It is possible to force woocommerce to show the related field invalid if the field is optional?

Jeroen Sormani October 20, 2018 at 10:53 am

Hi Attila,

Thats a good question..

I’ve done a bit of research and it is possible, but does require some additional customization and Javascript coding to accomplish something like that.
If wanted I’d be happy to discuss picking this customization up > feel free to reach out through the contact form to chat about it 😉

Cheers,
Jeroen

Michael November 2, 2018 at 5:57 pm

Can you validate a custom field with a remote url through an api?

For example.

Custom checkout field: giftcode

User enter his code on that field.

The code gets send through json on a remote url to check if it is valid.

If the json data that return has tha status code: valid then the giftcode field is valid.

Thank you.

Jeroen Sormani November 7, 2018 at 9:28 am

Hi Michael,

Its possible, but will require some additional work to accomplish that.

Cheers,
Jeroen

Richard November 7, 2018 at 3:32 am

Hi Jeroen great article, Im just wondering how to change the “name” attribute from the custom field. I see for example that woocommerce billing phone field has name=”billing_phone” and id=”reg_billing_phone”.

With this function it adds the same name for both attributes. Is this important? Im adding this field to the registration form and I want to use the same field woocommerce has.

Richard November 7, 2018 at 3:37 am

Nevermind, both id’s and names are the same ‘billing_phone’. Don’t know where did I see that lol.. Thanks anyway!

Mercedes Burgoa Somoza November 17, 2018 at 9:15 am

Hello,
I need the client’s fields to always appear blank in checkout page.

but I have not managed to do it. Could you give me a clue?

Thanks

Jeroen Sormani November 17, 2018 at 11:02 am

You could try to set the 'value' => '' element for each of the fields, though I’m not sure of the top of my head if that will work..

Mario November 18, 2018 at 6:56 pm

Hi Jeroen,
I wonder if you could help me out with this issue:

I created a custom field (select field) that appears on the Woocommerce checkout page and is named ‘client-type’. If I choose from the available values ‘personal’ or ‘company’ I want the available payment gateways to be changed (enabled/disabled).

The field already contains the classes ‘address-field’ and ‘update_totals_on_change’ and if I select an item (change the content), the order summary box is reloaded.

But now I’m lost… where do I get the selected value of my custom field to filter my gateways? I was hoping something similar like ‘WC()->customer->get_shipping_country()’ is working, but apparently I’m missing something…

Hope you have a hint for me
Mario

Chris December 6, 2018 at 5:12 pm

Hey Jeroen,

How would I reformat the billing phone number field to strip out any parentheses if someone tries to use in the phone number? I ‘m having issues with my shipping plugin if the phone has parentheses in them. Thanks!

Stef December 6, 2018 at 8:20 pm

Has anyone successfully disabled autocomplete in Chrome for a specific input? Like “Company”? We keep seeing customer’s year of birth being added there and it messes up the shipping label.

I’d love to disable that field only to not autocomplete. However, I believe Chrome still has a bug in it regarding autocomplete.

Bhavy Chauhan December 7, 2018 at 7:37 am

Hy can you tell me for adding custom field type file upload ?

Ali December 26, 2018 at 10:47 am

Hi
thanks for this useful article.
however , i added an extra phone field to the checkout page and it works fine , but i can’t see the new number that i get from user in order section.
what should i do ?
thanks

Simmons December 27, 2018 at 10:56 pm

Hi Jeroen ,
thank you for your guide. I’m looking for a way to show or hide custom checkout field based on the selected country.
Is there a way to do it?

Jeroen Sormani December 28, 2018 at 11:56 am

Hi Simmons,

Its possible to do that – it’d require some javascript to check for that and hide the custom field accordingly.
There’s not something build in Woo that would take care of that out of the box unfortunately.

– Jeroen

Ali January 21, 2019 at 12:26 pm

would you please answer my question ?
two comments above.
thanks a lot

PGA Ródenas February 5, 2019 at 12:22 pm

Thanks and congratulations is a great article.

David Langston February 21, 2019 at 12:24 pm

Hi Jeroen,

here’s the same question/ post but with your name spelled correctly, sorry about the previous typo : )

I am just getting into customising WooCommerce using code so this post is great for me.

I am having trouble at the moment overriding the default labels and placeholders on the checkout page which I’m trying to do via child theme functions.php

Sometimes my customisations load for a second and are then replaced again by the default WooCommerce labels and placeholders.

I realise it’s a little basic, but could you add an example of overriding labels and placeholders of default fields to this post?

Many thanks in advance and have a great day!

David

Jeroen Sormani March 20, 2019 at 4:47 pm

Hi David,

Without knowing the details, my best guess is that this has to do with the localisation WooCommerce does based on the country.
Whenever the country is switched WooCommerce will show the proper names for fields for that country, e.g. ‘Postcode’ or ‘Zipcode’, ‘State’ or ‘Province’ etc.

This may cause changes to the default placeholder to be overridden by this localisation.

I don’t have a code snippet at hand right now that solves this, but you can change the labels of localised fields accordingly using the ‘woocommerce_get_country_locale’ hook.

Cheers,
Jeroen

Will April 1, 2019 at 6:40 pm

How do you update an existing shipping field? I have a pop-up where the user selects a location from a list and registers a click event. How do I pass those WP_POST array to the checkout form shipping fields?

Finn April 14, 2019 at 8:00 pm

Hi, thanks for this great article!

Is there any possibility to check if the address fields (billing & shipping) contain a „forbidden“ word like „postbox“?

We can’t ship to postboxes and we don’t want to allow these inputs.

Thanks!

Jeroen Sormani April 15, 2019 at 10:43 am

Hi Finn,

Sure, its possible to create something like that. You can take a look at creating/adding a custom field validation;
https://jeroensormani.com/ultimate-guide-to-woocommerce-checkout-fields/#custom-checkout-field-validation

Cheers,
Jeroen

Adam Bell June 3, 2019 at 9:14 pm

Great piece. I’m using it and it works but I have two questions.

First, Id like to add not one, but two custom fields. I duplicated the array, made adjustments but only the first custom field appears at checkout. How do I do it for multiples?

Also, for SELECT menus, how is the data passed? I need to make it a string so the variables go to MailChimp, which only accept strings and not arrays.

Jeroen Sormani June 20, 2019 at 8:48 am

Hi Adam,

Make sure the two fields are given unique IDs.

Dropdowns are sending along strings in general, unless configured differently. If need be you can always format the array to a string before sending it over to MC.

Cheers,
Jeroen

wally June 20, 2019 at 1:57 am

hey Jeroen,

I want to put 3 class on the same line
I know first and last, wide for single one but how whats the class name to add a 3rd to have on the same line.

Wally

Jeroen Sormani June 20, 2019 at 8:47 am

Hi Wally,

I don’t think Woo has a class available out of the box that supports 3 fields on the same line. You’d want to create/add such CSS yourself to support that.

Cheers,
Jeroen

Leave a Reply