Customizing checkout fields using actions and filters

Customizing checkout fields using actions and filters should be a task you are comfortable with.

If you are unfamiliar with code and resolving potential conflicts, we have an extension that can help: WooCommerce Checkout Field Editor. Installing and activating this extension overrides any code below that you try to implement; and you cannot have custom checkout field code in your functions.php file when the extension is activated.

Note: This is a Developer level doc. We are unable to provide support for customizations under our Support Policy. If you are unfamiliar with code/templates and resolving potential conflicts, select a WooExpert or Developer  for assistance.

Custom code should be copied into your child theme’s functions.php  file.

How are checkout fields loaded to WooCommerce? ↑ Back to top

The billing and shipping fields for checkout pull from the countries class (class-wc-countries.php) and the get_address_fields function. This allows WooCommerce to enable/disable fields based on the user’s location.

Before returning these fields, WooCommerce puts the fields through a filter. This allows them to be edited by third-party plugins, themes and your own custom code.


$address_fields = apply_filters('woocommerce_billing_fields', $address_fields);


$address_fields = apply_filters('woocommerce_shipping_fields', $address_fields);

The checkout class adds the loaded fields to its ‘checkout_fields’ array, as well as adding a few other fields like “order notes”.

$this->checkout_fields['billing']    = $woocommerce->countries->get_address_fields( $this->get_value('billing_country'), 'billing_' );
$this->checkout_fields['shipping']   = $woocommerce->countries->get_address_fields( $this->get_value('shipping_country'), 'shipping_' );
$this->checkout_fields['account']    = array(
    'account_username' => array(
        'type' => 'text',
        'label' => __('Account username', 'woocommerce'),
        'placeholder' => _x('Username', 'placeholder', 'woocommerce')
    'account_password' => array(
        'type' => 'password',
        'label' => __('Account password', 'woocommerce'),
        'placeholder' => _x('Password', 'placeholder', 'woocommerce'),
        'class' => array('form-row-first')
    'account_password-2' => array(
        'type' => 'password',
        'label' => __('Account password', 'woocommerce'),
        'placeholder' => _x('Password', 'placeholder', 'woocommerce'),
        'class' => array('form-row-last'),
        'label_class' => array('hidden')
$this->checkout_fields['order']  = array(
    'order_comments' => array(
        'type' => 'textarea',
        'class' => array('notes'),
        'label' => __('Order Notes', 'woocommerce'),
        'placeholder' => _x('Notes about your order, e.g. special notes for delivery.', 'placeholder', 'woocommerce')

This array is also passed through a filter:

$this->checkout_fields = apply_filters('woocommerce_checkout_fields', $this->checkout_fields);

That means you have full control over checkout fields – you only need to know how to access them.

Overriding core fields ↑ Back to top

Hooking into the  woocommerce_checkout_fields filter lets you override any field. As an example, let’s change the placeholder on the order_comments fields. Currently, it’s set to:

_x('Notes about your order, e.g. special notes for delivery.', 'placeholder', 'woocommerce')

We can change this by adding a function to our theme functions.php file:

// Hook in
add_filter( 'woocommerce_checkout_fields' , 'custom_override_checkout_fields' );

// Our hooked in function - $fields is passed via the filter!
function custom_override_checkout_fields( $fields ) {
     $fields['order']['order_comments']['placeholder'] = 'My new placeholder';
     return $fields;

You can override other parts, such as labels:

// Hook in
add_filter( 'woocommerce_checkout_fields' , 'custom_override_checkout_fields' );

// Our hooked in function - $fields is passed via the filter!
function custom_override_checkout_fields( $fields ) {
     $fields['order']['order_comments']['placeholder'] = 'My new placeholder';
     $fields['order']['order_comments']['label'] = 'My new label';
     return $fields;

Or remove fields:

// Hook in
add_filter( 'woocommerce_checkout_fields' , 'custom_override_checkout_fields' );

// Our hooked in function - $fields is passed via the filter!
function custom_override_checkout_fields( $fields ) {

     return $fields;

Here’s a full list of fields in the array passed to woocommerce_checkout_fields:

  • Billing
    • billing_first_name
    • billing_last_name
    • billing_company
    • billing_address_1
    • billing_address_2
    • billing_city
    • billing_postcode
    • billing_country
    • billing_state
    • billing_email
    • billing_phone
  • Shipping
    • shipping_first_name
    • shipping_last_name
    • shipping_company
    • shipping_address_1
    • shipping_address_2
    • shipping_city
    • shipping_postcode
    • shipping_country
    • shipping_state
  • Account
    • account_username
    • account_password
    • account_password-2
  • Order
    • order_comments

Each field contains an array of properties:

  • type – type of field (text, textarea, password, select)
  • label – label for the input field
  • placeholder – placeholder for the input
  • class – class for the input
  • required – true or false, whether or not the field is require
  • clear – true or false, applies a clear fix to the field/label
  • label_class – class for the label element
  • options – for select boxes, array of options (key => value pairs)

In specific cases you need to use the woocommerce_default_address_fields filter. This filter is applied to all billing and shipping default fields:

  • country
  • first_name
  • last_name
  • company
  • address_1
  • address_2
  • city
  • state
  • postcode

For example, to make the address_1 field optional:

// Hook in
add_filter( 'woocommerce_default_address_fields' , 'custom_override_default_address_fields' );

// Our hooked in function - $address_fields is passed via the filter!
function custom_override_default_address_fields( $address_fields ) {
     $address_fields['address_1']['required'] = false;

     return $address_fields;

Defining select options ↑ Back to top

If you are adding a field with type ‘select’, as stated above you would define key/value pairs. For example:

$fields['billing']['your_field']['options'] = array(
  'option_1' => 'Option 1 text',
  'option_2' => 'Option 2 text'

Adding custom shipping and billing fields ↑ Back to top

Adding fields is done in a similar way to overriding fields. For example, let’s add a new field to shipping fields – shipping_phone:

It's alive!
It’s alive!

What do we do with the new field? Nothing. Because we defined the field in the checkout_fields array, the field is automatically processed and saved to the order post meta (in this case, _shipping_phone). If you want to add validation rules, see the checkout class where there are additional hooks you can use.

Adding a custom special field ↑ Back to top

To add a custom field is similar. Let’s add a new field to checkout, after the order notes, by hooking into the following:

This gives us:

WooCommerce Codex - Checkout Field Hook

Next we need to validate the field when the checkout form is posted. For this example the field is required and not optional:

A checkout error is displayed if the field is blank:

WooCommerce Codex - Checkout Field Notice

Finally, let’s save the new field to order custom fields using the following code:

The field is now saved to the order.

If you wish to display the custom field value on the admin order edition page, you can add this code:

This is the result:


Example: Make phone number not required ↑ Back to top

Adding custom fields to emails ↑ Back to top

To add a custom field value to WooCommerce emails — a completed order email, for example — use the following snippet:

WooCommerce - the most customizable eCommerce platform for building your online business.

Back to the top