Editing the WooCommerce checkout page at the code level gives you complete control over fields, validation, layout, and behavior. Unlike no-code plugins, custom code edits are lightweight, performant, and survive theme changes when placed in the right location.
This guide covers the developer approach to editing WooCommerce checkout using PHP hooks, filters, and custom validation. For no-code and visual customization methods, see our companion guide on customizing the WooCommerce checkout page.
Where to Place Custom Checkout Code
Never edit WooCommerce core files or your parent theme’s functions.php directly. Use one of these approaches:
- Custom plugin (recommended): Create a simple plugin file in
wp-content/plugins/my-checkout-edits/. This survives theme changes and is easy to disable - Child theme functions.php: Works but ties your customizations to one specific theme
- Code Snippets plugin: Quick for testing but harder to manage long-term
<?php
/**
* Plugin Name: Custom Checkout Edits
* Description: WooCommerce checkout field and validation customizations
* Version: 1.0.0
*/
if ( ! defined( 'ABSPATH' ) ) exit;
// Your checkout code goes here
The woocommerce_checkout_fields Filter
This is the most important hook for editing checkout fields. It gives you access to all billing, shipping, and order fields before they render.
Remove Fields
add_filter( 'woocommerce_checkout_fields', 'custom_remove_checkout_fields' );
function custom_remove_checkout_fields( $fields ) {
// Remove company name
unset( $fields['billing']['billing_company'] );
// Remove phone (use cautiously - some shipping providers need it)
unset( $fields['billing']['billing_phone'] );
// Remove order notes
unset( $fields['order']['order_comments'] );
// Remove Address Line 2
unset( $fields['billing']['billing_address_2'] );
unset( $fields['shipping']['shipping_address_2'] );
return $fields;
}
Make Fields Optional
add_filter( 'woocommerce_checkout_fields', 'custom_optional_fields' );
function custom_optional_fields( $fields ) {
// Make phone optional instead of removing it
$fields['billing']['billing_phone']['required'] = false;
// Make company optional
$fields['billing']['billing_company']['required'] = false;
return $fields;
}
Reorder Fields
add_filter( 'woocommerce_checkout_fields', 'custom_reorder_fields' );
function custom_reorder_fields( $fields ) {
// Move email to the top (lower priority = higher position)
$fields['billing']['billing_email']['priority'] = 5;
// Move phone after email
$fields['billing']['billing_phone']['priority'] = 6;
// Move first name after phone
$fields['billing']['billing_first_name']['priority'] = 10;
return $fields;
}
Add Custom Fields
add_filter( 'woocommerce_checkout_fields', 'custom_add_checkout_fields' );
function custom_add_checkout_fields( $fields ) {
$fields['billing']['billing_delivery_date'] = array(
'type' => 'date',
'label' => 'Preferred Delivery Date',
'required' => false,
'class' => array( 'form-row-wide' ),
'priority' => 120,
);
$fields['order']['gift_message'] = array(
'type' => 'textarea',
'label' => 'Gift Message',
'required' => false,
'class' => array( 'form-row-wide' ),
'placeholder' => 'Add a personal message (optional)',
);
return $fields;
}
// Save custom field values to order meta
add_action( 'woocommerce_checkout_update_order_meta', 'save_custom_checkout_fields' );
function save_custom_checkout_fields( $order_id ) {
if ( ! empty( $_POST['billing_delivery_date'] ) ) {
update_post_meta( $order_id, '_delivery_date',
sanitize_text_field( $_POST['billing_delivery_date'] ) );
}
if ( ! empty( $_POST['gift_message'] ) ) {
update_post_meta( $order_id, '_gift_message',
sanitize_textarea_field( $_POST['gift_message'] ) );
}
}
// Display custom fields in admin order view
add_action( 'woocommerce_admin_order_data_after_billing_address', 'display_custom_fields_admin' );
function display_custom_fields_admin( $order ) {
$delivery = get_post_meta( $order->get_id(), '_delivery_date', true );
$gift = get_post_meta( $order->get_id(), '_gift_message', true );
if ( $delivery ) {
echo '<p><strong>Delivery Date:</strong> ' . esc_html( $delivery ) . '</p>';
}
if ( $gift ) {
echo '<p><strong>Gift Message:</strong> ' . esc_html( $gift ) . '</p>';
}
}
Custom Checkout Validation
WooCommerce provides hooks to add custom validation rules before an order is placed.
Basic Validation Example
add_action( 'woocommerce_checkout_process', 'custom_checkout_validation' );
function custom_checkout_validation() {
// Require phone for physical products only
if ( WC()->cart->needs_shipping() && empty( $_POST['billing_phone'] ) ) {
wc_add_notice( 'Phone number is required for orders that need shipping.', 'error' );
}
// Validate delivery date is in the future
if ( ! empty( $_POST['billing_delivery_date'] ) ) {
$date = strtotime( sanitize_text_field( $_POST['billing_delivery_date'] ) );
if ( $date && $date < strtotime( 'tomorrow' ) ) {
wc_add_notice( 'Delivery date must be at least one day from now.', 'error' );
}
}
}
Conditional Validation Based on Cart Contents
add_action( 'woocommerce_checkout_process', 'validate_by_cart_contents' );
function validate_by_cart_contents() {
$has_subscription = false;
foreach ( WC()->cart->get_cart() as $item ) {
$product = $item['data'];
if ( $product->is_type( 'subscription' ) ) {
$has_subscription = true;
break;
}
}
// Require account creation for subscription products
if ( $has_subscription && ! is_user_logged_in() && empty( $_POST['createaccount'] ) ) {
wc_add_notice( 'An account is required for subscription products. Please check "Create an account".', 'error' );
}
}
Checkout Action Hooks for Custom Content
WooCommerce provides action hooks at specific positions in the checkout form. Use these to inject content without modifying templates.
Available Checkout Hooks
| Hook | Position | Use Case |
|---|---|---|
woocommerce_before_checkout_form | Before the entire form | Security notice, login prompt |
woocommerce_checkout_before_customer_details | Before billing/shipping | Express checkout buttons |
woocommerce_before_checkout_billing_form | Before billing fields | Billing instructions |
woocommerce_after_checkout_billing_form | After billing fields | Additional billing info |
woocommerce_before_checkout_shipping_form | Before shipping fields | Shipping notes |
woocommerce_before_order_notes | Before order notes | Custom sections |
woocommerce_review_order_before_submit | Before Place Order button | Terms, trust badges |
woocommerce_review_order_after_submit | After Place Order button | Guarantee text |
Adding Trust Badges Before the Payment Button
add_action( 'woocommerce_review_order_before_submit', 'add_trust_badges_checkout' );
function add_trust_badges_checkout() {
echo '<div class="checkout-trust-badges" style="text-align:center; padding:15px 0; margin:10px 0; border-top:1px solid #eee;">';
echo '<p style="font-size:13px; color:#666;">Secure checkout powered by 256-bit SSL encryption</p>';
echo '<p style="font-size:12px; color:#888;">30-day money-back guarantee | Free shipping over $75</p>';
echo '</div>';
}
Modifying the Checkout Template
For major layout changes, you can override WooCommerce templates in your child theme.
How to Override Checkout Templates
- Copy
wp-content/plugins/woocommerce/templates/checkout/form-checkout.php - Paste to
wp-content/themes/your-child-theme/woocommerce/checkout/form-checkout.php - Edit the copied file
Warning: Template overrides require maintenance. After every WooCommerce update, check if the original template changed and merge any differences. WooCommerce marks template version in the file header comment.
Disabling Fields for Digital Products
If you sell only digital products (downloads, licenses, courses), you can remove all shipping fields and simplify billing:
add_filter( 'woocommerce_checkout_fields', 'simplify_digital_checkout' );
function simplify_digital_checkout( $fields ) {
// Only simplify if cart has no physical products
if ( ! WC()->cart->needs_shipping() ) {
// Remove all shipping fields
unset( $fields['shipping'] );
// Remove unnecessary billing fields for digital
unset( $fields['billing']['billing_company'] );
unset( $fields['billing']['billing_address_1'] );
unset( $fields['billing']['billing_address_2'] );
unset( $fields['billing']['billing_city'] );
unset( $fields['billing']['billing_postcode'] );
unset( $fields['billing']['billing_country'] );
unset( $fields['billing']['billing_state'] );
unset( $fields['billing']['billing_phone'] );
}
return $fields;
}
HPOS Compatibility Note
If your store uses HPOS (High-Performance Order Storage), replace update_post_meta() and get_post_meta() with WooCommerce's order object methods:
// Instead of: update_post_meta( $order_id, '_delivery_date', $value );
// Use:
$order = wc_get_order( $order_id );
$order->update_meta_data( '_delivery_date', $value );
$order->save();
// Instead of: get_post_meta( $order_id, '_delivery_date', true );
// Use:
$order = wc_get_order( $order_id );
$delivery = $order->get_meta( '_delivery_date' );
This ensures your code works with both the legacy post-based storage and the new custom order tables. For more on HPOS and performance, see our guide on speeding up WooCommerce.
Testing Checkout Edits Safely
- Always test on staging first: Never deploy checkout code directly to production
- Test all payment gateways: Each gateway renders differently and may conflict with custom fields
- Test guest and logged-in checkout: Some hooks behave differently for authenticated users
- Test mobile devices: Custom fields must render correctly on small screens
- Monitor after WooCommerce updates: Major updates can change hook behavior or add new fields
FAQ
What is the main hook for editing WooCommerce checkout fields?
The woocommerce_checkout_fields filter gives you access to all checkout fields (billing, shipping, order). You can add, remove, reorder, or modify any field through this single filter.
How do I save custom checkout field data?
Use the woocommerce_checkout_update_order_meta action hook. Access the posted data from $_POST, sanitize it, and save with update_post_meta() or the HPOS-compatible $order->update_meta_data().
Can I add validation to checkout without a plugin?
Yes. Hook into woocommerce_checkout_process and use wc_add_notice() with type error to block checkout when validation fails.
Will custom code break after WooCommerce updates?
Hooks and filters are stable across updates. Template overrides may need updating when WooCommerce changes the original template. Always test on staging after major WooCommerce updates.
Should I use hooks or template overrides?
Use hooks whenever possible. They are more maintainable and less likely to break on updates. Only override templates when you need to fundamentally change the checkout HTML structure.
How do I make checkout edits compatible with block checkout?
The block checkout uses a different rendering system. Legacy woocommerce_checkout_fields filters do not apply to block checkout. For block checkout customization, use WooCommerce's Checkout Block Extension API or the Store API filters. This is still evolving in 2026.
Conclusion
Editing WooCommerce checkout with code gives you precise control that plugins cannot match. Start with the woocommerce_checkout_fields filter for field management, use action hooks for injecting content, and add custom validation to enforce business rules.
Keep your code in a custom plugin, test thoroughly on staging, and ensure HPOS compatibility for future-proof implementations.
Related reading: Customize Checkout (No-Code Methods) | WooCommerce Custom Product Fields | WooCommerce Fashion Store Setup
