<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ali Nawaz</title>
    <description>The latest articles on DEV Community by Ali Nawaz (@bealinawaz).</description>
    <link>https://dev.to/bealinawaz</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4203262%2F651ddc44-7551-4c4f-9201-d2cffaa527e7.jpg</url>
      <title>DEV Community: Ali Nawaz</title>
      <link>https://dev.to/bealinawaz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bealinawaz"/>
    <language>en</language>
    <item>
      <title>Custom fields on the WooCommerce Checkout block: why your old snippet does nothing</title>
      <dc:creator>Ali Nawaz</dc:creator>
      <pubDate>Mon, 02 Nov 2026 00:50:08 +0000</pubDate>
      <link>https://dev.to/bealinawaz/custom-fields-on-the-woocommerce-checkout-block-why-your-old-snippet-does-nothing-19e5</link>
      <guid>https://dev.to/bealinawaz/custom-fields-on-the-woocommerce-checkout-block-why-your-old-snippet-does-nothing-19e5</guid>
      <description>&lt;p&gt;If you added a checkout field with &lt;code&gt;woocommerce_checkout_fields&lt;/code&gt; or printed one on &lt;code&gt;woocommerce_after_order_notes&lt;/code&gt;, and it's simply not there on a newer store, the store is probably using the Checkout block. Since WooCommerce 8.3 (November 2023), new installs get the Cart and Checkout blocks by default.&lt;/p&gt;

&lt;p&gt;The Checkout block builds its form in React and talks to the Store API. Most classic checkout hooks never run there. WooCommerce's own hook compatibility table lists &lt;code&gt;woocommerce_after_order_notes&lt;/code&gt;, &lt;code&gt;woocommerce_before_order_notes&lt;/code&gt; and &lt;code&gt;woocommerce_review_order_before_submit&lt;/code&gt; as not supported, and &lt;code&gt;woocommerce_checkout_fields&lt;/code&gt; only partly. Validation on &lt;code&gt;woocommerce_checkout_process&lt;/code&gt; doesn't fire either, so an old snippet fails silently: no field, no error, no saved meta.&lt;/p&gt;

&lt;p&gt;The supported way is the Additional Checkout Fields API (stable since WooCommerce 8.9).&lt;/p&gt;

&lt;h2&gt;
  
  
  Register the field
&lt;/h2&gt;

&lt;p&gt;Put this in a small plugin rather than &lt;code&gt;functions.php&lt;/code&gt;, so the field survives a theme change.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="cd"&gt;/**
 * Plugin Name: My Checkout Fields
 * Requires Plugins: woocommerce
 */&lt;/span&gt;

&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'woocommerce_init'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;function_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'woocommerce_register_additional_checkout_field'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;woocommerce_register_additional_checkout_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'id'&lt;/span&gt;                &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'my-plugin/po-number'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'label'&lt;/span&gt;             &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Purchase order number'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'my-plugin'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s1"&gt;'optionalLabel'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Purchase order number (optional)'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'my-plugin'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s1"&gt;'location'&lt;/span&gt;          &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'order'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'type'&lt;/span&gt;              &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'text'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'attributes'&lt;/span&gt;        &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'maxLength'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'autocomplete'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'off'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s1"&gt;'sanitize_callback'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;strtoupper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;str_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="s1"&gt;'validate_callback'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;preg_match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'/^[A-Z0-9-]{4,20}$/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WP_Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'my-plugin-invalid-po'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Use 4 to 20 letters, numbers or dashes.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'my-plugin'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Things that trip people up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The ID needs a namespace&lt;/strong&gt; (&lt;code&gt;namespace/name&lt;/code&gt;). Without the slash, registration fails with a &lt;code&gt;_doing_it_wrong&lt;/code&gt; notice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;location&lt;/code&gt; decides storage.&lt;/strong&gt; &lt;code&gt;contact&lt;/code&gt; saves to the customer account, &lt;code&gt;address&lt;/code&gt; shows in both billing and shipping and saves two values, &lt;code&gt;order&lt;/code&gt; saves to the order only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Types:&lt;/strong&gt; &lt;code&gt;text&lt;/code&gt;, &lt;code&gt;select&lt;/code&gt;, &lt;code&gt;checkbox&lt;/code&gt;, and &lt;code&gt;date&lt;/code&gt; from WooCommerce 11.2.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Register on &lt;code&gt;woocommerce_init&lt;/code&gt; or later.&lt;/strong&gt; Since 11.0, registering before &lt;code&gt;after_setup_theme&lt;/code&gt; triggers a notice.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Validate from somewhere else
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;validate_callback&lt;/code&gt; runs after &lt;code&gt;sanitize_callback&lt;/code&gt;. For checks added by another plugin, use the action, and add to the &lt;code&gt;WP_Error&lt;/code&gt; you're given. Returning a new one does nothing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'woocommerce_validate_additional_field'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kt"&gt;WP_Error&lt;/span&gt; &lt;span class="nv"&gt;$errors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'my-plugin/po-number'&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nv"&gt;$key&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nb"&gt;strpos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'TEST'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$errors&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'my-plugin-test-po'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Test PO numbers are not accepted.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'my-plugin'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One naming trap: the docs call the order-location hook &lt;code&gt;woocommerce_blocks_validate_location_other_fields&lt;/code&gt;, but the 11.2.1 source fires &lt;code&gt;woocommerce_blocks_validate_location_order_fields&lt;/code&gt;. If a cross-field rule never runs, check which name your version fires.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read the value
&lt;/h2&gt;

&lt;p&gt;The admin order screen, thank-you page and order emails show the field without extra code. In your own code, use the helper instead of raw meta keys:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Automattic\WooCommerce\Blocks\Package&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'woocommerce_store_api_checkout_order_processed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kt"&gt;WC_Order&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Package&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;container&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nc"&gt;CheckoutFields&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$po&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$fields&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_field_from_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'my-plugin/po-number'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'other'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$po&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;add_order_note&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'PO number: %s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$po&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the group &lt;code&gt;'other'&lt;/code&gt; for contact and order fields, and &lt;code&gt;'billing'&lt;/code&gt; or &lt;code&gt;'shipping'&lt;/code&gt; for address fields. And don't wait for &lt;code&gt;woocommerce_checkout_create_order&lt;/code&gt;: it doesn't fire on the block checkout.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the classic checkout is still the right call
&lt;/h2&gt;

&lt;p&gt;The API only adds fields. Keep the shortcode checkout if you need to change or remove core fields, use a type that isn't listed (file upload, radio, textarea), show an address field in only one address, or run an extension that only supports the classic form. Fields registered with this API don't appear on the shortcode checkout, so that route still needs classic hooks.&lt;/p&gt;

&lt;p&gt;I wrote a longer version with a select and a checkbox example, the cross-field validation hook, the meta keys for export tools, and an FAQ on my blog: &lt;a href="https://bealinawaz.com/blog/add-custom-field-woocommerce-block-checkout/" rel="noopener noreferrer"&gt;How to add a custom field to WooCommerce block checkout&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>woocommerce</category>
      <category>wordpress</category>
      <category>php</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
