I recently took over a WooCommerce project for a client whose last developer had vanished. The site was old, cluttered with plugins, and the checkout was a mess. The client had a simple request: add a custom “Delivery Instructions” field to the checkout and save it to the order. It sounded like an hour or two of work.
Famous last words. When I dug into their functions.php, it was a real mess. A tangled web of hooks, deprecated filters, and anonymous functions with no comments. My gut reaction, the craftsman in me, was to tell the client we needed to burn it all down and refactor the entire checkout. I started mapping it out, a full project to modernize the code and put in a proper class structure. It looked great in my head, and it would have taken a week.
But the client did not need a masterpiece of modern architecture. They needed a text box. This is the classic developer trap. I have watched junior devs fall into it, and if I am honest, it is one I still have to stop myself from falling into after 14 years. Jason Cohen once called it the “love of creation” in a post I still think about, which I found through an old article on carlalexander.ca. We love to build clean, perfect things, but perfect is often the enemy of shipped.
Pragmatic PHP development and the refactor trap
The client’s budget was for a few hours, not a full week. Pitching a massive refactor wasn’t just overkill; it was tone-deaf. It didn’t solve their immediate problem and would have tanked their budget. The pragmatic solution isn’t always the most elegant one, but it’s the one that respects the client’s reality.
So I threw out my grand refactoring plan. Instead of trying to fix everything, I looked for the single correct point of entry. I needed one hook to add the field and another to save the data, and that was it. There was no need to rewrite their entire checkout flow. I just had to slot in my small piece of functionality without touching the rest.
After a bit of digging, the right hooks turned out to be woocommerce_after_order_notes to display the field and woocommerce_checkout_create_order to save the data. The code is not revolutionary, but it is clean and isolated, and it works without disturbing the hornet’s nest of legacy code around it.
/**
* Add the custom field to the checkout page.
*/
add_action( 'woocommerce_after_order_notes', 'bbioon_add_delivery_instructions_field' );
function bbioon_add_delivery_instructions_field( $checkout ) {
woocommerce_form_field( 'delivery_instructions', array(
'type' => 'textarea',
'class' => array('form-row-wide'),
'label' => __('Delivery Instructions'),
'placeholder' => __('Special instructions for the delivery driver.'),
), $checkout->get_value( 'delivery_instructions' ));
}
/**
* Save the custom field to the order meta.
*/
add_action( 'woocommerce_checkout_create_order', 'bbioon_save_delivery_instructions_field', 10, 2 );
function bbioon_save_delivery_instructions_field( $order, $data ) {
if ( ! empty( $data['delivery_instructions'] ) ) {
$order->update_meta_data( '_delivery_instructions', sanitize_textarea_field( $data['delivery_instructions'] ) );
}
}
So, what’s the point?
The real fix here was not the code. It was the mindset. It is about knowing the difference between what is possible and what is necessary. We are paid to solve business problems, not to build our personal software cathedrals.
- Identify the actual need: the client needed a text field, not a new checkout system.
- Find the path of least resistance: work with the system, even when it is messy. Find the right hook or filter to get the job done without breaking everything else.
- Ship the solution: a working feature delivered on time beats a “perfect” refactor that never gets finished.
This stuff gets complicated fast. If you are tired of debugging someone else’s mess and just want your site to work, drop my team a line. We have probably seen it before.
Could that checkout still use a refactor? Sure, and I have made a note to recommend it to the client for a later phase. But for now, we solved the problem and shipped it, and that is the win.