How are checkout fields loaded into WooCommerce?
The fields for the checkout (billing and shipping) come from the countries class (class-wc-countries.php) and theget_address_fields function- this is so locale settings are applied to the fields letting WooCommerce enable/disable fields based on the users location.
Before returning the fields, WC will run the fields through a filter. This allows them to be edited by third party plugins and themes (and your own custom code).
Billing:
$address_fields = apply_filters('woocommerce_billing_fields', $address_fields);
Shipping:
$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);
This means you have full control over checkout fields – you just need to know how to access them!
Lesson 1 – Overriding core fields
Hooking into the woocommerce_checkout_fields filter will let you override any field. As a demonstration, lets try changing the placeholder on the order_comments fields. Currently its set to:
_x('Notes about your order, e.g. special notes for delivery.', 'placeholder', 'woocommerce')
We can change this by adding a simple 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; }
Thats it! You can override other parts too, like the 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; }
You can even remove fields entirely:
// 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 ) { unset($fields['order']['order_comments']); return $fields; }
Heres 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
- 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)
Lesson 2 – Adding custom shipping and billing fields
Adding fields is done in a simular way to overriding fields. As an example, lets add a new field to the shipping fields – shipping_phone
// 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['shipping']['shipping_phone'] = array( 'label' => __('Phone', 'woocommerce'), 'placeholder' => _x('Phone', 'placeholder', 'woocommerce'), 'required' => false, 'class' => array('form-row-wide'), 'clear' => true ); return $fields; }

It's alive!
Now we have this new field, what do we do with it? We don’t need to do anything! Because we’re defined the field in the checkout_fields array the field will be automatically processed and saved to the order post meta (in this case the field will be _shipping_phone). If you want to add extra validation rules see the checkout class; there are additional hooks in there you can use.
Lesson 3 – Adding a custom special field
We’ve covered adding fields to billing/shipping/checkout fields but what if we want something more custom? Lets add a new field to the checkout, we can add this new field after order notes by hooking into the following:
/** * Add the field to the checkout **/ add_action('woocommerce_after_order_notes', 'my_custom_checkout_field'); function my_custom_checkout_field( $checkout ) { echo '<div id="my_custom_checkout_field"><h3>'.__('My Field').'</h3>'; woocommerce_form_field( 'my_field_name', array( 'type' => 'text', 'class' => array('my-field-class form-row-wide'), 'label' => __('Fill in this field'), 'placeholder' => __('Enter something'), ), $checkout->get_value( 'my_field_name' )); echo '</div>'; }
This will give us:
Next we need to validate the field when the checkout form gets posted. For the sake of this example we’ll make it required:
/** * Process the checkout **/ add_action('woocommerce_checkout_process', 'my_custom_checkout_field_process'); function my_custom_checkout_field_process() { global $woocommerce; // Check if set, if its not set add an error. if (!$_POST['my_field_name']) $woocommerce->add_error( __('Please enter something into this new shiny field.') ); }
This will show an error on the checkout if the field is left blank:
Finally, lets save the new field to the order custom fields using the following code:
/** * Update the order meta with field value **/ add_action('woocommerce_checkout_update_order_meta', 'my_custom_checkout_field_update_order_meta'); function my_custom_checkout_field_update_order_meta( $order_id ) { if ($_POST['my_field_name']) update_post_meta( $order_id, 'My Field', esc_attr($_POST['my_field_name'])); }
Sorted! The field will now be saved to the order.