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 –
- Configure a Node workflow with package.json file
- Configure Webpack for combining, transpiling, and minifying your extension’s JavaScript
- Add block.json file
- Create a src directory to organize the JavaScript files you write
- Register the scripts for custom block
- 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 –
- name – Name of the block. It will be in the format <namespace>/block-name
- version – Version of the block
- title – The title of block which will be displayed in the editor
- category – The category in which the block will be present.
- 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”
- attributes – Defines whether we can move or remove our inner block
- 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.
This to the shop owners who are running or planning to run BOGO offers on their WooCommerce store…
BOGO deals are great for increasing your sales, but have you thought about which offers are bringing you more revenue and which offers are not performing that great?
Don’t just set a BOGO deal, track the revenue generated by your deals in real-time with the Flexi BOGO for WooCommerce plugin.
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 –
- get_name() – returns the name of our block
- initialize() – any initialization or setup for the block ( registering the scripts )
- get_script_handles() – returns the array of script handles to enqueue in frontend
- get_editor_script_handles() – returns the array of script handles to enqueue in editor
- 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.
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 –
- 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.
- woocommerce_store_api_register_update_callback – PHP callback function which executes when cart is updated with extensionCartUpdate.
when I use updated npm and nvm I get below error.
Please refer screenshot
Hi Shweta,
It appears that the error you’re encountering is related to your specific system setup. While we cannot provide direct support for system-specific issues, I suggest looking into Node.js related forums or support channels for additional troubleshooting steps.
which npm,nvm,reakit version schould be used so that this code works perfect
Hi Shweta,
It is recommended that you install the latest version of everything to ensure the code is compatible.
How can we implement validation in this, say i want to collect 2nd email id using some custom field and i want to do server side validation before order can be saved to make sure inserted text is correct email id only and if not then give a error and stop the checkout process and show that error message
Hi Raj,
To add custom validation for custom fields in the WooCommerce checkout blocks, you can try with the woocommerce_blocks_validate_additional_field_%s filter. This filter lets you define validation rules for the fields you’ve added. For detailed information and examples, you can refer to this GitHub Link.
Thank, In this article you have added the field differently, so the validation method that you have pointed out will not work on the added by your way. woocommerce_blocks_validate_additional_field_%s will only work on the field that we will be adding using latest way of adding field in woocommerce block
Hi Raj,
For validation messages on clicking ‘Place order’ button, you can use the hook woocommerce_store_api_checkout_update_order_from_request as mentioned in the post and throw an Exception with the error message if the email address is not in correct format.
If you want to add the validation on the field itself, you can use setValidationErrors method in JS.
Thanks will try it out
Thanks I was able to get it to work based on your instruction but now i am stuck in another issue my code auto insert a single line of text in woocommerce checkout information block it works when we use normal theme (non block based theme) but when we use block based theme it fails to auto insert and there is not error below is the screenshot of my code if you can guide me what i am doing wrong in this that will be great it works with normal theme like Twenty Twenty but it dont work with Block… Read more »
Hi Rashmi
I am not an experienced porgrammer and I apologize for that, but I appreciated your post so much, clear and detailed.
I have downloaded and installed the plugin but I have a problem, in edit checkout page I see the GIFT field, but when then saved and published in frontend page I don’t find the GIFT field.
Could you please point me to some reason or solution.
Thank you very much and boun work
Hi MaiAvuta, We’ve tested & the code works perfectly well to display the custom input field both on the edit page and on the frontend as well. One possible reason could be, if you downloaded the zip directly from github, the folder name would be ‘checkout-block-example-main‘ as it also includes the github branch name. Could you rename the plugin folder to ‘checkout-block-example’ and then check if its working? If not, try these possible solutions. Switch to a default WordPress theme and deactivate all plugins except WooCommerce to avoid any theme/plugin conflicts. If the issue persists, let me know if you… Read more »
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.