WooCommerce’s default checkout is a fine starting point and a terrible ending point. Every store I’ve built — and I’ve built over thirty — has needed at least five customizations to the checkout flow before launch. Sometimes it’s removing fields that don’t apply to the business. Sometimes it’s adding fields the business actually needs. Sometimes it’s redesigning the entire layout because the default two-column shows order summary on the right and your customer is on mobile.
This is the snippet collection I copy from on every WooCommerce build. Each one is production-ready code I’ve shipped to live stores. Drop them into your child theme’s functions.php, a custom plugin, or the Code Snippets plugin. Group them however you want — they’re modular.
Fields: remove, add, reorder, relabel
1. Remove the company name field
add_filter( 'woocommerce_checkout_fields', function( $fields ) {
unset( $fields['billing']['billing_company'] );
return $fields;
} );
2. Remove the second address line
add_filter( 'woocommerce_checkout_fields', function( $fields ) {
unset( $fields['billing']['billing_address_2'] );
return $fields;
} );
3. Remove the order notes textarea
add_filter( 'woocommerce_enable_order_notes_field', '__return_false' );
4. Make phone optional instead of required
add_filter( 'woocommerce_checkout_fields', function( $fields ) {
$fields['billing']['billing_phone']['required'] = false;
return $fields;
} );
5. Add a custom field (delivery instructions)
add_filter( 'woocommerce_checkout_fields', function( $fields ) {
$fields['order']['delivery_instructions'] = [
'type' => 'textarea',
'label' => __( 'Delivery instructions', 'mytheme' ),
'placeholder' => 'Gate code, drop-off preferences, etc.',
'required' => false,
'class' => [ 'form-row-wide' ],
'priority' => 100,
];
return $fields;
} );
add_action( 'woocommerce_checkout_update_order_meta', function( $order_id ) {
if ( ! empty( $_POST['delivery_instructions'] ) ) {
update_post_meta( $order_id, '_delivery_instructions',
sanitize_textarea_field( $_POST['delivery_instructions'] ) );
}
} );
6. Reorder fields with priority
add_filter( 'woocommerce_checkout_fields', function( $fields ) {
$fields['billing']['billing_first_name']['priority'] = 10;
$fields['billing']['billing_last_name']['priority'] = 20;
$fields['billing']['billing_email']['priority'] = 30;
$fields['billing']['billing_phone']['priority'] = 40;
return $fields;
} );
7. Relabel “Place order” to a clearer CTA
add_filter( 'woocommerce_order_button_text', function() {
return 'Pay Securely Now';
} );
8. Change “Your order” heading
add_filter( 'gettext', function( $translated, $original, $domain ) {
if ( 'Your order' === $original && 'woocommerce' === $domain ) {
return 'Order Summary';
}
return $translated;
}, 20, 3 );
9. Make a required field optional based on country
add_filter( 'woocommerce_checkout_fields', function( $fields ) {
if ( isset( $_POST['billing_country'] ) && $_POST['billing_country'] === 'PK' ) {
$fields['billing']['billing_state']['required'] = false;
}
return $fields;
} );
10. Pre-fill fields for logged-in users
add_filter( 'woocommerce_checkout_get_value', function( $value, $input ) {
if ( ! is_user_logged_in() ) return $value;
$user_id = get_current_user_id();
$custom = get_user_meta( $user_id, $input, true );
return $custom ?: $value;
}, 10, 2 );
Validation rules
11. Force phone to be 10+ digits
add_action( 'woocommerce_checkout_process', function() {
$phone = preg_replace( '/[^0-9]/', '', $_POST['billing_phone'] ?? '' );
if ( strlen( $phone ) < 10 ) {
wc_add_notice( 'Please enter a valid phone number (10+ digits).', 'error' );
}
} );
12. Block disposable email domains
add_action( 'woocommerce_checkout_process', function() {
$email = sanitize_email( $_POST['billing_email'] ?? '' );
$blocked = [ 'mailinator.com', 'tempmail.com', '10minutemail.com', 'guerrillamail.com' ];
foreach ( $blocked as $bad ) {
if ( stripos( $email, $bad ) !== false ) {
wc_add_notice( 'Please use a real email address.', 'error' );
}
}
} );
13. Minimum order total
add_action( 'woocommerce_checkout_process', function() {
$min = 25;
if ( WC()->cart->total < $min ) {
wc_add_notice(
sprintf( 'Minimum order is $%s.', $min ), 'error'
);
}
} );
14. Maximum quantity per product
add_filter( 'woocommerce_quantity_input_args', function( $args, $product ) {
$args['max_value'] = 5;
return $args;
}, 10, 2 );
Payment + shipping logic
15. Hide a payment gateway based on cart total
add_filter( 'woocommerce_available_payment_gateways', function( $gateways ) {
if ( is_admin() ) return $gateways;
if ( WC()->cart->total > 500 && isset( $gateways['cod'] ) ) {
unset( $gateways['cod'] );
}
return $gateways;
} );
16. Free shipping over a threshold (without the coupon)
add_filter( 'woocommerce_package_rates', function( $rates, $package ) {
if ( WC()->cart->subtotal >= 100 ) {
foreach ( $rates as $rate_id => $rate ) {
if ( $rate->method_id !== 'free_shipping' ) {
unset( $rates[ $rate_id ] );
}
}
}
return $rates;
}, 10, 2 );
17. Hide shipping methods for specific countries
add_filter( 'woocommerce_package_rates', function( $rates, $package ) {
if ( ( $package['destination']['country'] ?? '' ) === 'PK' ) {
unset( $rates['flat_rate:1'] );
}
return $rates;
}, 10, 2 );
18. Force a default payment gateway
add_filter( 'woocommerce_available_payment_gateways', function( $gateways ) {
if ( WC()->session ) {
WC()->session->set( 'chosen_payment_method', 'stripe' );
}
return $gateways;
} );
UX improvements
19. Auto-update checkout when fields change
add_filter( 'woocommerce_update_order_review_fragments', function( $fragments ) {
ob_start();
woocommerce_order_review();
$fragments['.woocommerce-checkout-review-order-table'] = ob_get_clean();
return $fragments;
} );
20. Add a progress indicator above the form
add_action( 'woocommerce_before_checkout_form', function() {
echo '<div class="checkout-steps">'
. '<span class="active">1. Details</span> → '
. '<span>2. Payment</span> → '
. '<span>3. Confirmation</span>'
. '</div>';
}, 5 );
21. Show trust badges below the place order button
add_action( 'woocommerce_review_order_after_submit', function() {
echo '<div class="checkout-trust">'
. '🔒 256-bit SSL encryption · 💳 Stripe verified · ↩️ 30-day refund'
. '</div>';
} );
22. Remove the “Login” notice for guest checkout
add_filter( 'woocommerce_checkout_login_message', '__return_empty_string' );
23. Add a coupon link below the order total
add_action( 'woocommerce_review_order_before_payment', function() {
if ( wc_coupons_enabled() ) {
echo '<a href="#" class="showcoupon">Have a coupon code?</a>';
}
} );
24. Persist cart for 30 days
add_filter( 'wc_session_expiring', fn() => 60 * 60 * 24 * 30 - 60 );
add_filter( 'wc_session_expiration', fn() => 60 * 60 * 24 * 30 );
Admin-side: capturing the custom data
25. Display custom field in admin order screen
add_action( 'woocommerce_admin_order_data_after_billing_address', function( $order ) {
$instr = get_post_meta( $order->get_id(), '_delivery_instructions', true );
if ( $instr ) {
echo '<p><strong>Delivery instructions:</strong> ' . esc_html( $instr ) . '</p>';
}
} );
26. Include custom field in admin email
add_filter( 'woocommerce_email_order_meta_fields', function( $fields, $sent_to_admin, $order ) {
$instr = get_post_meta( $order->get_id(), '_delivery_instructions', true );
if ( $instr ) {
$fields['delivery_instructions'] = [
'label' => 'Delivery instructions',
'value' => $instr,
];
}
return $fields;
}, 10, 3 );
27. Include custom field in CSV export
add_filter( 'woocommerce_order_export_csv_data', function( $row, $order ) {
$row['delivery_instructions'] = get_post_meta( $order->get_id(), '_delivery_instructions', true );
return $row;
}, 10, 2 );
Conversion lifts
28. Strip the State/County field for Pakistan to reduce form length
add_filter( 'woocommerce_get_country_locale', function( $locale ) {
$locale['PK']['state']['required'] = false;
$locale['PK']['state']['hidden'] = true;
return $locale;
} );
29. Save abandoned-cart emails
add_action( 'woocommerce_checkout_update_order_review', function( $post_data ) {
parse_str( $post_data, $data );
if ( ! empty( $data['billing_email'] ) ) {
WC()->session->set( 'pending_email', sanitize_email( $data['billing_email'] ) );
// Forward to your email tool here (Klaviyo, Mailchimp, n8n webhook).
}
} );
30. Auto-apply a coupon from a URL parameter
add_action( 'wp_loaded', function() {
if ( ! empty( $_GET['coupon'] ) && ! is_admin() ) {
WC()->cart->apply_coupon( sanitize_text_field( $_GET['coupon'] ) );
}
} );
31. Bonus: redirect to a custom thank-you page per product
add_action( 'woocommerce_thankyou', function( $order_id ) {
$order = wc_get_order( $order_id );
foreach ( $order->get_items() as $item ) {
if ( $item->get_product_id() === 123 ) {
wp_safe_redirect( '/thanks-premium/' );
exit;
}
}
}, 5 );
How I use these in real client builds
On every WooCommerce build I do at CofCode, the first day after install is spent removing default fields that don’t apply (company, second address line, often order notes), adding the one or two fields that do apply (delivery instructions, GST number, gift message), and renaming the place-order button to something more conversion-friendly. That’s snippets 1-8 right there.
For B2B clients we layer in 9-14 (validation, minimum order value, max quantity, country-specific logic). For high-volume D2C stores we focus on 19-24 (UX polish, trust badges, persistent carts). The result is a checkout that converts better than the default by 10-25% on most stores, with zero plugin bloat.
If you want the same pattern shipped to your store, the case studies page has live examples. Or just reach out with your current checkout URL and I’ll send back three specific lifts I’d recommend.
When to stop with snippets and rebuild the checkout
If you’re more than 15 snippets deep and the checkout still feels janky, it’s time for a custom checkout. WooCommerce’s block-based checkout (introduced in WC 8.3) is much more flexible than the legacy shortcode-based one — but it also requires React knowledge to extend properly. For most small-to-mid stores the shortcode + snippets approach is fine. For stores doing $100k/month+, a custom React-based checkout is usually worth the investment.
Either way, start with the right field set, validate aggressively, and remove anything the customer doesn’t strictly need to complete the purchase. The default checkout is a developer’s compromise; your checkout should be a buyer’s path of least resistance.





