Complete Guide: Adding Custom Fields to WooCommerce Checkout

Complete Guide: Adding Custom Fields to WooCommerce Checkout

In this comprehensive guide, I'll show you how to add custom fields to your WooCommerce checkout page and display them in the order details, admin area, and emails. This solution is fully reusable - you can easily add any type of field by simply modifying a configuration array.

Note: All code should be added to your child theme's functions.php file or a custom plugin.

Why Add Custom Checkout Fields?

Custom checkout fields allow you to collect additional information from customers during checkout. Common uses include:

  • Special delivery instructions
  • Gift messages
  • Business registration numbers
  • Preferred delivery time slots
  • Custom product personalization

Step 1: Add Custom Fields to Checkout Page

This code adds a text field and a select dropdown to your checkout page. You can easily add more fields by extending the $checkout_fields array.

/**
 * Add custom fields to WooCommerce checkout
 */
add_action('woocommerce_after_order_notes', 'add_custom_checkout_fields');
function add_custom_checkout_fields($checkout) {
    echo '<div id="custom_checkout_fields"><h3>' . __('Additional Information', 'text-domain') . '</h3>';

    // Define your fields here (modify as needed)
    $checkout_fields = array(
        'custom_text_field' => array(
            'type'        => 'text',
            'label'       => __('Custom Text Field', 'text-domain'),
            'placeholder' => __('Enter your text here...', 'text-domain'),
            'required'    => false,
            'class'      => array('form-row-wide'),
        ),
        'custom_select_field' => array(
            'type'        => 'select',
            'label'       => __('Custom Select Field', 'text-domain'),
            'options'     => array(
                ''          => __('Select an option...', 'text-domain'),
                'option1'   => __('Option 1', 'text-domain'),
                'option2'   => __('Option 2', 'text-domain'),
            ),
            'required'    => true,
            'class'      => array('form-row-wide'),
        ),
        // Add more fields as needed
    );

    // Loop through and render fields
    foreach ($checkout_fields as $field_key => $field_args) {
        woocommerce_form_field($field_key, $field_args, $checkout->get_value($field_key));
    }

    echo '</div>';
}

Field Configuration Options

Parameter Description Example Values
type The type of form field text, textarea, select, checkbox, radio, etc.
label The field label displayed to customers __('Gift Message', 'text-domain')
placeholder Placeholder text inside the field __('Enter your message...', 'text-domain')
required Whether the field is mandatory true or false
class CSS classes for styling array('form-row-wide'), array('form-row-first')
options For select fields - key/value pairs array('option1' => 'Value 1', 'option2' => 'Value 2')

Step 2: Save Custom Checkout Fields

/**
 * Save custom checkout fields to order meta
 */
add_action('woocommerce_checkout_create_order', 'save_custom_checkout_fields', 10, 2);
function save_custom_checkout_fields($order, $data) {
    $checkout_fields = array(
        'custom_text_field',
        'custom_select_field',
        // Add more field keys here
    );

    foreach ($checkout_fields as $field_key) {
        if (!empty($_POST[$field_key])) {
            $order->update_meta_data('_' . $field_key, sanitize_text_field($_POST[$field_key]));
        }
    }
}

Step 3: Display Fields in Frontend Order Details

/**
 * Display custom fields in frontend order view
 */
add_action('woocommerce_order_details_after_order_table', 'display_custom_fields_frontend_order');
function display_custom_fields_frontend_order($order) {
    $custom_fields = array(
        '_custom_text_field' => __('Custom Text Field', 'text-domain'),
        '_custom_select_field' => __('Custom Select Field', 'text-domain'),
        // Add more fields here
    );

    echo '<h3>' . __('Additional Information', 'text-domain') . '</h3>';
    echo '<table class="woocommerce-table shop_table additional_info">';
    echo '<tbody>';

    foreach ($custom_fields as $meta_key => $label) {
        $value = $order->get_meta($meta_key);
        if ($value) {
            echo '<tr>';
            echo '<th>' . esc_html($label) . ':</th>';
            echo '<td>' . esc_html($value) . '</td>';
            echo '</tr>';
        }
    }

    echo '</tbody></table>';
}

Step 4: Display Fields in Admin Order View

/**
 * Display custom fields in admin order view
 */
add_action('woocommerce_admin_order_data_after_billing_address', 'display_custom_fields_admin_order');
function display_custom_fields_admin_order($order) {
    $custom_fields = array(
        '_custom_text_field' => __('Custom Text Field', 'text-domain'),
        '_custom_select_field' => __('Custom Select Field', 'text-domain'),
        // Add more fields here
    );

    echo '<div class="order_data_column" style="width:100%;">';
    echo '<h3>' . __('Additional Information', 'text-domain') . '</h3>';

    foreach ($custom_fields as $meta_key => $label) {
        $value = $order->get_meta($meta_key);
        if ($value) {
            echo '<p><strong>' . esc_html($label) . ':</strong> ' . esc_html($value) . '</p>';
        }
    }

    echo '</div>';
}

Step 5: Display Fields in Order Emails

/**
 * Display custom fields in order emails
 */
add_filter('woocommerce_email_order_meta_fields', 'display_custom_fields_in_emails', 10, 3);
function display_custom_fields_in_emails($fields, $sent_to_admin, $order) {
    $custom_fields = array(
        '_custom_text_field' => __('Custom Text Field', 'text-domain'),
        '_custom_select_field' => __('Custom Select Field', 'text-domain'),
        // Add more fields here
    );

    foreach ($custom_fields as $meta_key => $label) {
        $value = $order->get_meta($meta_key);
        if ($value) {
            $fields[$meta_key] = array(
                'label' => $label,
                'value' => $value,
            );
        }
    }

    return $fields;
}

Advanced Customizations

Adding Different Field Types

Here are examples of different field types you can add:

Checkbox Field

'newsletter_optin' => array(
    'type'        => 'checkbox',
    'label'       => __('Subscribe to newsletter', 'text-domain'),
    'required'    => false,
    'class'      => array('form-row-wide'),
)

Textarea Field

'special_instructions' => array(
    'type'        => 'textarea',
    'label'       => __('Special Instructions', 'text-domain'),
    'placeholder' => __('Any special delivery instructions...', 'text-domain'),
    'required'    => false,
    'class'      => array('form-row-wide'),
)

Conditional Fields

You can show/hide fields based on other selections using JavaScript:

// Add this to your theme's JS file
jQuery(document).ready(function($) {
    $('#custom_select_field').change(function() {
        if ($(this).val() === 'special_option') {
            $('#custom_text_field').closest('.form-row').show();
        } else {
            $('#custom_text_field').closest('.form-row').hide();
        }
    }).trigger('change');
});

Practical Example: Advanced Select Field Implementation

Let's explore a more practical example of a select field that customers might actually use during checkout - a "Delivery Time Preference" selector.

This example shows how to create a time slot selection dropdown with grouped options.

/**
 * Add delivery time preference field to checkout
 */
add_action('woocommerce_after_order_notes', 'add_delivery_time_field');
function add_delivery_time_field($checkout) {
    echo '<div id="delivery_time_selection"><h3>' . __('Delivery Preferences', 'text-domain') . '</h3>';

    woocommerce_form_field('delivery_time_preference', array(
        'type'        => 'select',
        'label'       => __('Preferred Delivery Time', 'text-domain'),
        'required'    => true,
        'class'       => array('form-row-wide'),
        'options'     => array(
            ''          => __('Select your preferred time...', 'text-domain'),
            'morning'   => __('Morning (8am-12pm)', 'text-domain'),
            'afternoon' => __('Afternoon (12pm-5pm)', 'text-domain'),
            'evening'   => __('Evening (5pm-9pm)', 'text-domain'),
            'specific'  => __('Specific Time (We\'ll call to confirm)', 'text-domain'),
        ),
    ), $checkout->get_value('delivery_time_preference'));

    echo '</div>';
    
    // Add conditional field for specific time
    woocommerce_form_field('delivery_phone_number', array(
        'type'        => 'tel',
        'label'       => __('Contact Number for Specific Time', 'text-domain'),
        'placeholder' => __('+1 (123) 456-7890', 'text-domain'),
        'required'    => false,
        'class'       => array('form-row-wide', 'conditional-field'),
    ), $checkout->get_value('delivery_phone_number'));
}

/**
 * Show/hide conditional field with JavaScript
 */
add_action('wp_footer', 'delivery_time_conditional_logic');
function delivery_time_conditional_logic() {
    if (!is_checkout()) return;
    ?>
    <script>
    jQuery(document).ready(function($) {
        // Hide initially
        $('.conditional-field').closest('.form-row').hide();
        
        // Show when specific time is selected
        $('#delivery_time_preference').change(function() {
            if ($(this).val() === 'specific') {
                $('.conditional-field').closest('.form-row').slideDown();
            } else {
                $('.conditional-field').closest('.form-row').slideUp();
            }
        }).trigger('change');
    });
    </script>
    <?php
}

/**
 * Validate the conditional field when needed
 */
add_action('woocommerce_checkout_process', 'validate_delivery_fields');
function validate_delivery_fields() {
    if ($_POST['delivery_time_preference'] === 'specific' && empty($_POST['delivery_phone_number'])) {
        wc_add_notice(__('Please provide a contact number for specific time delivery', 'text-domain'), 'error');
    }
}

Key Features of This Implementation

Feature Description Implementation
Grouped Time Options Logical grouping of delivery windows Morning/Afternoon/Evening options
Special Case Option "Specific Time" with different handling Triggers additional field
Conditional Logic Show phone field only when needed JavaScript toggle
Validation Require phone when specific time selected woocommerce_checkout_process hook

Frontend Display Enhancement

To make the selected option more visible in order details, modify the display function:

// In your display_custom_fields_frontend_order function
if ($meta_key === '_delivery_time_preference') {
    $time_options = array(
        'morning'   => __('Morning (8am-12pm)', 'text-domain'),
        'afternoon' => __('Afternoon (12pm-5pm)', 'text-domain'),
        'evening'   => __('Evening (5pm-9pm)', 'text-domain'),
        'specific'  => __('Specific Time', 'text-domain'),
    );
    
    $value = isset($time_options[$value]) ? $time_options[$value] : $value;
    
    if ($value === __('Specific Time', 'text-domain')) {
        $phone = $order->get_meta('_delivery_phone_number');
        if ($phone) {
            $value .= ' (' . esc_html($phone) . ')';
        }
    }
}
Pro Tip: For time-sensitive deliveries, consider adding this information to the WooCommerce order admin email notifications using the woocommerce_email_after_order_table hook to ensure store staff see it immediately.

Troubleshooting

If your fields aren't appearing:

  1. Clear WooCommerce transients (WooCommerce → Status → Tools)
  2. Check for JavaScript errors in browser console
  3. Verify field keys match between all functions
  4. Test with a default theme to rule out theme conflicts

Conclusion

This complete solution gives you full control over custom checkout fields in WooCommerce. The modular approach makes it easy to add, remove, or modify fields by simply updating the configuration arrays. The fields will appear consistently throughout the order process - from checkout to order confirmation to admin management.

Remember to:

  • Replace 'text-domain' with your actual text domain
  • Test thoroughly after making changes
  • Consider field validation for complex requirements
  • Backup your site before making significant changes