Black Friday & Cyber Monday SUPER SALE ALL WEEK:
Grab 40% OFF on plugins
Days
Hours
Minutes
Seconds

How to Add Custom Fields in WooCommerce Checkout Block?

Custom input field in Checkout block

WooCommerce introduced cart & checkout blocks based on WordPress gutenberg blocks. Adding custom fields in the checkout block is not as straightforward as adding it on a normal checkout page using WooCommerce hooks. Checkout block is a parent block and the recommended way to add custom fields is to add it as inner blocks.

We can create custom inner blocks which can be added, deleted, moved or locked to a place. In this blog, we will see how we can add a simple input field to the checkout block and save the value to the database.

Overview –

  1. Configure a Node workflow with package.json file 
  2. Configure Webpack for combining, transpiling, and minifying your extension’s JavaScript
  3. Add block.json file
  4. Create a src directory to organize the JavaScript files you write
  5. Register the scripts for custom block
  6. Save the value to database

In the below example, we will be adding a text field named ‘Gift Message’ in the checkout block.

Add a package.json file to configure Node workflow

The package.json file contains all the dependencies we use for our project. We need to add all the libraries we are going to use in this file and install them to our project. 

We can create this file by running the following command in the terminal

npm init

Node will prompt you to enter the basic information for your project like name, version, description, author name, etc. Once the package.json file is created, we need to add the following dependencies in it –

"devDependencies": {
          "@woocommerce/block-library": "^2.3.0",
          "@woocommerce/dependency-extraction-webpack-plugin": "^2.2.0",
          "@woocommerce/eslint-plugin": "^2.2.0",
          "@wordpress/blocks": "^12.6.0",
          "@wordpress/scripts": "^26.0.0",
          "@wordpress/components": "^23.8.0"
}

These are the essential dependencies to use for creating the inner blocks. The next step is to install these dependencies with the command –

npm install

This command tells the Node to install all the dependencies we have added in the package.json file with the necessary version numbers.

Another property we need to add in package.json is the scripts property. 

"scripts": {
          "build": "wp-scripts build",
          "format": "wp-scripts format",
          "lint:css": "wp-scripts lint-style",
          "lint:js": "wp-scripts lint-js",
          "packages-update": "wp-scripts packages-update",
          "plugin-zip": "wp-scripts plugin-zip",
          "start": "wp-scripts start"
}

As you can see, each property in scripts corresponds to a command Node will run when we call npm run [command].

wp-scripts is a command from the @wordpress/scripts package.  The build command is used to make the code production ready. It will create a build folder in the project with the necessary JS & CSS files.

Configure Webpack

Webpack is used to compile all modules into minified assets. It is a part of @wordpress/scripts package, so we don’t need to install it separately. Webpack is used to create and maintain organized, modular JavaScript that is still easy to load in web browsers.

Create a file called webpack.config.js in the root directory of the plugin.  In this file, we’ll build a configuration that tells Webpack how it should process the files in our plugin.

const defaultConfig = require('@wordpress/scripts/config/webpack.config');

const WooCommerceDependencyExtractionWebpackPlugin = require('@woocommerce/dependency-extraction-webpack-plugin');

We will use dependency extraction plugin that lets us tell Webpack which dependencies our plugin expects to already be available in the environment. And these dependencies don’t need to be added in our minified assets too.

Add block.json file

The block.json file contains the metadata for our custom block. We need to add the following details in this file –

  1. name – Name of the block. It will be in the format <namespace>/block-name
  2. version – Version of the block
  3. title – The title of block which will be displayed in the editor
  4. category – The category in which the block will be present.
  5. parent – Parent block name. As we are creating an inner block, we will need to mention under which parent block it will be present. As we want to display our field in shipping address section, we will use the parent block “woocommerce/checkout-shipping-address-block”
  6. attributes – Defines whether we can move or remove our inner block
  7. editorScript – The starting point of the script. We have included the build folder here, which will contain all the js files built for production with wp-scripts.
{
    "$schema": "https://schemas.wp.org/trunk/block.json",
    "apiVersion": 2,
    "name": "checkout-block-example/gift-message",
    "version": "1.0.0",
    "title": "Gift Message",
    "category": "woocommerce",
    "parent": [ "woocommerce/checkout-shipping-address-block" ],
    "attributes": {
        "lock": {
            "type": "object",
            "default": {
                "remove": true,
                "move": true
            }
        }
    },
    "textdomain": "checkout-block-example",
    "editorScript": "file:./build/index.js"
}

Now that the basic setup is done, let’s create a block which will add an input field for ‘Gift Message’ on the checkout block.

Create a src directory to organize javascript files

The default WordPress webpack configuration looks for the file at src/index.js by default. This is the main entry point for the rest of the code. 

This folder will contain all the react based code we will add for our checkout inner block. Let’s create a file called index.js in which we will register our block in the block editor.

To register the inner block we will be using registerBlockType function. This function is imported from @wordpress/blocks package.

import { registerBlockType } from '@wordpress/blocks';
import { SVG } from '@wordpress/components';
import { Edit } from './edit';
import metadata from './block.json';


registerBlockType(metadata, {
    icon: {
        src: (
            <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
	        // Add SVG for your block icon
	    </SVG>
        ),
        foreground: '#874FB9',
    },
    edit: Edit
});

We pass in the metadata from block.json in the registerBlockType(). The second parameter is an object which has a property called ‘icon’ to define the icon for our block and another property called ‘edit’ where we define what we will display in the block on the edit checkout page block editor.

We have added a component called ‘Edit’ in the ‘edit’ property but haven’t defined it yet. Let’s create the component which will display our field in the block editor. 

import { ValidatedTextInput } from '@woocommerce/blocks-checkout';
import {
    useBlockProps,
} from '@wordpress/block-editor';


import { __ } from '@wordpress/i18n';


export const Edit = ({ attributes, setAttributes }) => {
    const blockProps = useBlockProps();
    return (
        <div {...blockProps}>
            <div className={ 'example-fields' }>
                <ValidatedTextInput
                        id="gift_message"
                        type="text"
                        required={false}
                        className={'gift-message'}
                        label={
                            'Gift Message'
                        }
                        value={ '' }
                />
            </div>
        </div>
    );
};

The useBlockProps() adds any extra classNames to the wrapper element so that the gutenberg editor can manipulate the wrapper element. 

We, then add a text input with the component called ‘ValidatedTextInput’. This component is part of the @woocommerce/blocks-checkout package. ‘ValidatedTextInput’ passes props such as id, type, required, className, label and value as shown in the code snippet.

We have now added the code for adding the field in the block editor. Now, let’s add the input field on the frontend. Create a file called ‘frontend.js’ in the src directory. To display our block we need to register it first on the frontend. 

import metadata from './block.json';
import { ValidatedTextInput } from '@woocommerce/blocks-checkout';
import { __ } from '@wordpress/i18n';


// Global import
const { registerCheckoutBlock } = wc.blocksCheckout;


const Block = ({ children, checkoutExtensionData }) => {
    return (
        <div className={ 'example-fields' }>
                <ValidatedTextInput
                        id="gift_message"
                        type="text"
                        required={false}
                        className={'gift-message'}
                        label={
                            'Gift Message'
                        }
                        value={ '' }
                />
        </div>
    )
}


const options = {
    metadata,
    component: Block
};


registerCheckoutBlock( options );

In the above code, we created a component called ‘Block’ which has 2 parameters – children & checkoutExtensionData. We have added the ValidatedTextInput in the same way as we did for the block editor. We should always return a single parent element, otherwise it will result in an error.

Once we have created the block, we need to pass this in the registerCheckoutBlock() function along with the metadata of our block. registerCheckoutBlock() registers our block and displays it on the frontend.

After the changes are made in the JS files, we need to create a build which will transform the code into a JS code which the browsers understand. Run the command – npm run build

This command will in turn call the wp-scripts build as we defined in package.json. It will create a build folder with the javascript files to be used in production. It will also create a file called index.asset.php & checkout-block-frontend.asset.php (based on our webpack config), which contains all the dependencies we used in the code using the import statements.

<?php return array('dependencies' => array('wc-blocks-checkout', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n'), 'version' => '75f87e6cc72beaa3efd7');
<?php return array('dependencies' => array('wc-blocks-checkout', 'wp-element', 'wp-i18n'), 'version' => 'c853b0af192c76f9e476');

At this point, we have added the input field in the block editor as well as the frontend but it will not be displayed until we register our scripts.

Register the scripts for the custom inner block

Next step is to register these JS files and dependencies and enqueue them on admin side & frontend. To make our CSS/JS files available to our scripts, we need to use something called ‘IntegrationRegistry’ to register an  IntegrationInterface. This will be a class where we will enqueue all of our styles & scripts.

We use ‘woocommerce_blocks_checkout_block_registration’ which passes an instance of IntegrationRegistry. We create a class ‘Blocks_Integration’ which implements IntegrationInterface, which contains these functions – 

  1. get_name()  – returns the name of our block
  2. initialize() – any initialization or setup for the block ( registering the scripts )
  3. get_script_handles() – returns the array of script handles to enqueue in frontend
  4. get_editor_script_handles() – returns the array of script handles to enqueue in editor
  5. get_script_data() – returns an array of key, value pairs of data made available to the block on the client side.

When we register the scripts, we include the dependencies from *.asset.php files created during build process. The JS/CSS files from the build folder should be enqueued while registering the scripts.

For adding scripts in block editor –

public function register_block_editor_scripts() {
        $script_path       = '/build/index.js';
        $script_url        = plugins_url( 'checkout-block-example' . $script_path );
        $script_asset_path = plugins_url( 'checkout-block-example/build/index.asset.php' );
        $script_asset      = file_exists( $script_asset_path )
            ? require $script_asset_path
            : array(
                'dependencies' => array(),
                'version'      => $this->get_file_version( $script_asset_path ),
            );


        wp_register_script(
            'gift-message-block-editor',
            $script_url,
            $script_asset['dependencies'],
            $script_asset['version'],
            true
        );
    }

Adding scripts on frontend –

public function register_block_frontend_scripts() {
        $script_path       = '/build/checkout-block-frontend.js';
        $script_url        = plugins_url( '/checkout-block-example' . $script_path );
        $script_asset_path = WP_PLUGIN_DIR . '/checkout-block-example/build/checkout-block-frontend.asset.php';


        $script_asset = file_exists( $script_asset_path )
            ? require $script_asset_path
            : array(
                'dependencies' => array(),
                'version'      => $this->get_file_version( $script_asset_path ),
            );


        wp_register_script(
            'checkout-block-frontend',
            $script_url,
            $script_asset['dependencies'],
            $script_asset['version'],
            true
        );
    }

As you can see, we have added the *.asset.php as the dependency array for the scripts. We just need to pass the script handles ‘gift-message-block-editor’ and ‘checkout-block-frontend’ in the functions get_editor_script_handles() & get_script_handles() respectively.

Now that our block is registered, build created and scripts are enqueued, our block is finally  displayed on the checkout page.

How to Add Custom Fields in WooCommerce Checkout Block? - Tyche Softwares
Custom inner block in block editor
How to Add Custom Fields in WooCommerce Checkout Block
Custom inner block on Checkout page

Saving data in checkout block

As of now, we have only displayed our input field on the checkout block. We also need to save the data entered by the customer. 

First step is to expose our data to the store API with the help of ExtendSchema.  It allows us to add our data to any of the store endpoints. The data we add here is namespaced to our plugin so that our data does not conflict with other data.

We will use woocommerce_store_api_register_endpoint_data() to make our data available on the checkout endpoint.

It takes in an array as a parameter where we define the data & its schema to expose our data in the store API.

use Automattic\WooCommerce\StoreApi\StoreApi;
use Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema;
use Automattic\WooCommerce\StoreApi\Schemas\V1\CheckoutSchema;


woocommerce_store_api_register_endpoint_data(
                array(
                    'endpoint'        => CheckoutSchema::IDENTIFIER,
                    'namespace'       => 'checkout-block-example',
                    'data_callback'   => 'cb_data_callback',
                    'schema_callback' => 'cb_schema_callback',
                    'schema_type'     => ARRAY_A,
                )
            );

We need to add 2 callback functions – data_callback & schema_callback. The data callback function will return an array of key value pairs which we need to add in the API. In the below snippet we have defined our key as ‘gift_message’ and its default value is blank.

function cb_data_callback() {
    return array(
        'gift_message' => '',
    );
}

The schema callback function will return an array of each key’s schema like what will be the type of the value, its description etc. We have defined the schema for our data key ‘gift_message’. It will be of type string and if not set it will be null.

function cb_schema_callback() {
    return array(
        'gift_message'  => array(
            'description' => __( 'Gift Message', 'checkout-block-example' ),
            'type'        => array( 'string', 'null' ),
            'readonly'    => true,
        ),
    );
}

Now that we have exposed our data in the checkout API, we need to set its value on checkout when the customer enters it. In our Block component, we have a parameter called ‘checkoutExtensionData’ which defines a function called setExtensionData. We are going to use setExtensionData to set the value of our input field. The format of the function is –

setExtensionData( <plugin-namespace>, <key>, <value> )

Let’s update our ‘Block’ component and add an onChange event on our input field –

const Block = ({ children, checkoutExtensionData }) => {
    const [ giftMessage, setGiftMessage ] = useState('');
    const { setExtensionData } = checkoutExtensionData;


    const onInputChange = useCallback(
        ( value ) => {
            setGiftMessage( value );
            setExtensionData( 'checkout-block-example', 'gift_message', value );
        },
        [ setGiftMessage. setExtensionData ]
    )


    return (
        <div className={ 'example-fields' }>
                <ValidatedTextInput
                        id="gift_message"
                        type="text"
                        required={false}
                        className={'gift-message'}
                        label={
                            'Gift Message'
                        }
                        value={ giftMessage }
                        onChange={ onInputChange }
                />
        </div>
    )
}

As you can see, we have added an onChange event on input field with callback function onInputChange. We set the value using setExtensionData. This value will be added in the checkout API under the property called ‘extensions’.

extensions: {
	checkout-block-example {
		gift_message: <entered value here>
	}
}

Whenever we make any changes to the JS/CSS files, we need to run the build command to generate the build files.

The final step is to save this value in the order meta when the order is placed. We cannot use normal WooCommerce hooks here. We will be using ‘woocommerce_store_api_checkout_update_order_from_request’ hook which is executed when the place order button is clicked.

This hook has 2 parameters – $order & $request

The $request parameter contains the data from the checkout API. The data which we have set is accessible under ‘extensions’ > ‘checkout-block-example’. 

public function __construct() {
        add_action( 'woocommerce_store_api_checkout_update_order_from_request', array( &$this, 'update_block_order_meta' ), 10, 2 );
    }


    public static function update_block_order_meta( $order, $request ) {
        $data = isset( $request['extensions']['checkout-block-example'] ) ? $request['extensions']['checkout-block-example'] : array();


        $order->update_meta_data( 'Gift Message', $data['gift_message'] );


    }

We fetch the gift_message value from the $request data and save it in order meta. We can then display this value on order received page, My Account page, emails etc using the regular WooCommerce hooks.

Notes

This was a basic setup for creating a custom inner block in the Checkout block. You can add multiple input fields in a single block or add multiple inner blocks. Check the whole source code for the above example here.

Here are a few functions which can be helpful in creating blocks –

  1. extensionCartUpdate – This is a trigger to update checkout and send the updated values to API. This can be used to add fees with our custom block.
  2. woocommerce_store_api_register_update_callback – PHP callback function which executes when cart is updated with extensionCartUpdate.

References

  1. Modern JavaScript and WooCommerce 101
  2. Tutorial: Adding React Support to a WooCommerce Extension
  3. Available Extensibility Interfaces for the Cart and Checkout blocks
  4. Extensibility in WooCommerce Blocks

Browse more in: Code Snippets, WooCommerce How Tos, WooCommerce Tutorials

Share It:

Subscribe
Notify of
15 Comments
Newest
Oldest
Inline Feedbacks
View all comments
JanaLEE
1 month ago

Hi

I followed these steps to build the WooCommerce Block component, it works fine.

However, there are some questions.

First, how can I set the component allow only add one item.

Second, does the component can be deleted? It looks like the component cannot be deleted.

sumit
2 months ago

I am getting an error at run build time.
Error :
1. https://prnt.sc/wNPcoM-EUawm
2 https://prnt.sc/Cm9Znhvqr-vJ (webpack.config.js)

How i can resolve this issue ?

Nick
2 months ago

In your plugin, the input is automatically displayed in the checkbox page. How to turn off automatic input displayed?

I mean so I can add input using Gutenberg.

Thank you.

Gabe
3 months ago

Hi, thx for tutorial! It was informative.
Unfortunately, PlugIn what I download. dont work. The field ‘Gift message’ does not appear. I don’t change the code, I only download the plugin.

Gabe
3 months ago

Thank you for answer.

I made my component according to your tutorial. And my block did appear among Checkout’s child blocks. And that’s great! But, for some reason, my block was transparent and not clickable. If you look at the code, the button has an attribute like aria-disabled=”true”. Maybe you understand where exactly I made a mistake?

Vito
4 months ago

Hi great tutorial – works well. Is there a way to validate the field? If yes how? Also, is there away to put the data from a custom field it under actual order in woocommerce (rather as a meta field?) Thanks.

15
0
Would love your thoughts, please comment.x
()
x