woocommerce_form_field() Function: Custom Fields [Guide]

WooCommerce Form Field

Customizing WooCommerce forms usually leads to the same question: how do I add a field that actually renders, validates, and saves? The answer is almost always woocommerce_form_field(), the helper WooCommerce uses to output its own checkout and account inputs.

Using the helper keeps your custom inputs consistent with the rest of the form. This guide covers its parameters, shows how to add a gift message field to checkout, and points out the mistakes that break saving.

What Is woocommerce_form_field()?

It is a global WooCommerce function that outputs one complete form field: the label, the input, the description, and the error wrapper. WooCommerce calls it throughout the checkout, My Account address, and registration forms, so using it gives your fields the same markup and CSS classes as the built-in ones.

Function Signature and Parameters

The function takes three arguments:

  • $key is the field name. It also becomes the key you read from $_POST.
  • $args is an array of settings that control how the field looks and behaves.
  • $value is optional and pre-fills the input, usually from the checkout or user object.

Key Arguments

These are the arguments you will use most often:

ArgumentPurposeExample
typeInput type: text, email, tel, select, radio, checkbox, textarea, password, country, or state'textarea'
labelVisible label text__( 'Gift message', 'textdomain' )
requiredAdds the required marker and enforces a non-empty valuefalse
classWrapper classes. form-row-wide spans the full widtharray( 'form-row-wide' )
placeholderPlaceholder text inside the input'Happy birthday, Jane!'
descriptionHelp text below the input'Printed on a card. 200 characters max.'
optionsKey/value pairs for select and radio fieldsarray( 'a' => 'A' )
validateBuilt-in validators such as phone, email, or postcodearray( 'phone' )
custom_attributesExtra HTML attributes on the input, such as maxlengtharray( 'maxlength' => 200 )
returnReturns the HTML instead of echoing it when truetrue
prioritySort order within the form120

Basic Usage

By default, the function echoes the markup. Pass 'return' => true when you need the HTML as a string, for example to combine it with other output.

<?php
woocommerce_form_field(
    'order_gift_message',
    array(
        'type'              => 'textarea',
        'label'             => __( 'Gift message', 'textdomain' ),
        'required'          => false,
        'class'             => array( 'form-row-wide' ),
        'placeholder'       => __( 'Happy birthday, Jane!', 'textdomain' ),
        'custom_attributes' => array( 'maxlength' => 200 ),
    )
);

Tip: Use form-row-wide for full-width fields like textareas or notes. For two fields side by side, use form-row-first and form-row-last so the layout matches the built-in address fields.

Add a Custom Checkout Field

A working field needs three pieces: a render hook, a validation hook, and a save hook. Place the snippets in a child theme’s functions.php or a site-specific plugin. The example targets the classic shortcode checkout. The checkout optimization guide covers when extra fields are worth the friction. The WooCommerce checkout fields guide covers how to add, remove, relabel, and reorder fields before implementing custom field logic.

Render the Field

woocommerce_after_order_notes runs after the order notes field, so the gift message input appears at the end of the checkout form.

<?php
add_action( 'woocommerce_after_order_notes', 'andriy_add_gift_message_field' );
function andriy_add_gift_message_field( $checkout ) {
    woocommerce_form_field(
        'order_gift_message',
        array(
            'type'              => 'textarea',
            'label'             => __( 'Gift message', 'textdomain' ),
            'required'          => false,
            'class'             => array( 'form-row-wide' ),
            'custom_attributes' => array( 'maxlength' => 200 ),
        ),
        $checkout->get_value( 'order_gift_message' )
    );
}

Validate the Field

woocommerce_checkout_process fires when the customer submits the order. Read the input with wp_unslash(), then add an error notice with wc_add_notice() to block submission. The maxlength attribute only limits typing in the browser, so the server-side check is still required.

<?php
add_action( 'woocommerce_checkout_process', 'andriy_validate_gift_message' );
function andriy_validate_gift_message() {
    if ( empty( $_POST['order_gift_message'] ) ) {
        return;
    }

    $message = wp_unslash( $_POST['order_gift_message'] );

    if ( mb_strlen( $message ) > 200 ) {
        wc_add_notice( __( 'Gift messages can be up to 200 characters.', 'textdomain' ), 'error' );
    }
}

Save the Field to the Order

Save the value with woocommerce_checkout_create_order, which passes the order object before it is stored. Writing through $order->update_meta_data() works with both legacy post-based storage and High-Performance Order Storage (HPOS), the default on new WooCommerce installs. sanitize_textarea_field() keeps line breaks, which sanitize_text_field() would strip.

<?php
add_action( 'woocommerce_checkout_create_order', 'andriy_save_gift_message' );
function andriy_save_gift_message( $order ) {
    if ( ! empty( $_POST['order_gift_message'] ) ) {
        $order->update_meta_data( '_order_gift_message', sanitize_textarea_field( wp_unslash( $_POST['order_gift_message'] ) ) );
    }
}

Read the value back with $order->get_meta( '_order_gift_message' ). To display it on the thank-you page, follow the approach in the order received page guide.

Common Mistakes

Most failures come from a few mismatches:

  • Mismatched names. The $key must match the $_POST key you read later. A field named order_gift_message is read from $_POST['order_gift_message'].
  • Saving too early. Writing in woocommerce_checkout_process runs before the order exists. Use woocommerce_checkout_create_order instead.
  • Skipping sanitization. Pass input through sanitize_textarea_field() or wc_clean(), and escape output with esc_html().
  • Expecting the block checkout to pick it up. The block checkout ignores these hooks and uses its own additional-fields API.

FAQs About woocommerce_form_field()

Does woocommerce_form_field() Echo or Return HTML?

It echoes the markup by default. Pass 'return' => true in the arguments array to get the HTML as a string instead.

Can I Use It Outside Checkout?

Yes. WooCommerce uses it on My Account address and registration forms too. Outside checkout, you still need your own save handler, such as update_user_meta() on the form submission.

Why Is My Field Missing From the Order?

The usual causes are a field name that does not match the $_POST key, or a save hook attached to the wrong action. Confirm the name in the rendered HTML, then check that the save hook runs on woocommerce_checkout_create_order.

Does It Work With the Block Checkout?

No. The block-based checkout registers extra fields through its own API, so these snippets target the classic shortcode checkout.

Conclusion

Pair woocommerce_form_field() with a validation hook and a save hook, and a custom field works end to end with the same markup WooCommerce uses for its own inputs. Keep the number of checkout fields small, because every extra field can slow down the purchase. Selling digital products only? See this hide checkout fields for virtual products guide.

Need a custom checkout or form built for your store? Hire a WooCommerce developer, or reach out to me directly.

About The Author

Picture of Andriy Haydash

Andriy Haydash

Andriy Haydash is a WordPress Expert who helps people build and launch successful WordPress membership and e-learning websites.

Note: Not all of the articles are written directly by me.
Affiliate Disclaimer: Some links in the post may be my affiliate links

The Ultimate Managed Hosting Platform

Before YOU Leave...
Join My Newsletter

Get practical tips & tricks on how to start, grow and market your course/membership site.