WooCommerce Cart and Checkout Blocks: Extending with Inner Block Slots

WooCommerce’s Cart and Checkout blocks expose a set of registered inner block slots — named extension points where third-party plugins can inject their own blocks without forking core templates. The @woocommerce/blocks-checkout package provides the registerCheckoutBlock API and a set of named slot components (ExperimentalOrderMeta, ExperimentalDiscountsMeta, etc.) that map to these slots.

Problem: WooCommerce Blocks checkout replaces the classic checkout shortcode — custom fields, validation, and order meta added via classic hooks like woocommerce_checkout_fields no longer work, and there is no obvious replacement API.

Solution: Use the WooCommerce Blocks IntegrationInterface and register_checkout_field() API (WooCommerce 8.6+) to add fields to the contact, address, or order section. Handle server-side validation with the woocommerce_blocks_validate_location_*_fields action and save to order meta with woocommerce_blocks_checkout_order_processed.


The example below registers a custom "Delivery Instructions" text field that appears inside the Checkout block's shipping address section, saves the value as order meta, and displays it in the WooCommerce admin order screen.


// js/checkout-extension/index.js
const { registerPlugin }        = wp.plugins;
const { ExperimentalOrderMeta } = wc.blocksCheckout;
const { extensionCartUpdate }   = wc.blocksCheckout;
const { TextareaControl }       = wp.components;
const { useState }              = wp.element;

function DeliveryInstructionsSlot() {
    const [ note, setNote ] = useState( '' );

    const handleChange = ( value ) => {
        setNote( value );
        // Push the value into cart extensions so it travels to the server
        extensionCartUpdate( {
            namespace: 'my-plugin',
            data:      { delivery_note: value },
        } );
    };

    return wp.element.createElement( ExperimentalOrderMeta, null,
        wp.element.createElement( TextareaControl, {
            label:    'Delivery instructions (optional)',
            value:    note,
            onChange: handleChange,
            rows:     3,
        } )
    );
}

registerPlugin( 'my-plugin-checkout-extension', {
    render: DeliveryInstructionsSlot,
    scope:  'woocommerce-checkout',
} );


<?php
// Save delivery note from cart extensions to order meta
add_action( 'woocommerce_store_api_checkout_order_processed',
    function ( WC_Order $order ) {
        $request = WC()->session ? WC()->session->get( 'store_api_draft_order' ) : null;

        // Retrieve from cart extensions (set by extensionCartUpdate)
        $cart       = WC()->cart;
        $extensions = $cart ? $cart->get_cart_meta( 'extensions' ) : [];
        $note       = sanitize_textarea_field(
            $extensions['my-plugin']['delivery_note'] ?? ''
        );

        if ( $note ) {
            $order->update_meta_data( '_delivery_note', $note );
            $order->save();
        }
    }
);

// Display in admin order screen
add_action( 'woocommerce_admin_order_data_after_shipping_address',
    function ( WC_Order $order ) {
        $note = $order->get_meta( '_delivery_note' );
        if ( $note ) {
            echo '<p><strong>' . esc_html__( 'Delivery instructions:', 'my-plugin' ) . '</strong><br>'
                . nl2br( esc_html( $note ) ) . '</p>';
        }
    }
);


NOTE: extensionCartUpdate() triggers a Store API cart update on every call, so debounce the handler with a short delay (200–300 ms) on onChange to avoid flooding the server with a network request for every character the user types.