WPBakery Page Builder is a popular visual editor for WordPress. It allows users to build pages without coding. Developers can extend it with custom widgets. Custom widgets add new features and unique layouts. This guide shows how to create them step by step.
For this guide, we will use a real WordPress example. I developed a custom section for the Listlab website. The section displays information about their products. It pulls product data and formats it for the layout. This example shows how custom widgets can be useful in real projects.

How to create the custom widget for WPBakery Page Builder?
To create a custom widget, we need a specific hook. We will use the vc_before_init hook. This hook runs before WPBakery loads its core components. During this hook, we call our custom function. The function registers a new widget for WPBakery. This allows the widget to appear inside the builder interface.
add_action( 'vc_before_init', 'cxc_home_products_items_funct' );
Our function cxc_home_products_items_funct does not receive any parameters. It runs without input values. Inside this function, we call the vc_map function. The vc_map function accepts an associative array. This array contains all widget configuration settings. These settings define the name, base shortcode, category, and other options. This is how WPBakery learns about our new widget.
At this stage, our code has this look:
//Our code
add_action( 'vc_before_init', 'cxc_home_products_items_funct' );
function cxc_home_products_items_funct() {
vc_map(
array(
"name" => __("Home Products", "my-text-domain"), // Element name
"base" => "home_products", // Element shortcode
"class" => "home_products",
"category" => __('List Labs Components', 'my-text-domain'),
"icon" => "my_bartag",
'params' => array(
array(
'type' => 'param_group',
'param_name' => 'home_products_items',
'params' => array(
array(
"type" => "attach_image",
"holder" => "img",
"class" => "",
"heading" => __( "Image", "my-text-domain" ),
"param_name" => "home_products_items_img",
"value" => __( "", "my-text-domain" ),
),
array(
"type" => "textfield",
"holder" => "div",
"class" => "",
"heading" => __( "Title", "my-text-domain" ),
"param_name" => "home_products_items_title",
"value" => __( "", "my-text-domain" ),
),
array(
"type" => "vc_link",
"holder" => "a",
"class" => "",
"heading" => __( "Link", "my-text-domain" ),
"param_name" => "home_products_items_link",
"value" => __( "", "my-text-domain" ),
)
)
),
)
)
);
}
The name parameter defines the widget title. WPBakery displays this title in the element list.
The base parameter defines the shortcode name. WPBakery uses this value to generate the element code.
The class parameter sets a CSS class. This class can help with styling or targeting the widget.
The category parameter groups the widget inside the builder. It allows better organization for custom components.
The icon parameter sets an icon for the widget. The icon appears inside the WPBakery panel.
The params parameter defines all input fields of the widget. These fields allow users to customize the widget output.
Inside params, we have a param_group type. A param_group allows repeatable sets of fields. It is useful for lists or product items.
The param_name inside the group defines the storage key. WPBakery stores user values under this key.
Inside the group, the first field is attach_image. This field allows image uploads. WPBakery stores the selected image ID.
The field also has heading. The heading sets the label displayed in the editor.
The param_name for this field defines the key for the image.
The next field is textfield. This field stores text input. We use it for product titles.
The last field is vc_link. This field stores a URL and link options. It allows products to link to internal or external pages.
All these parameters allow flexible input. They make the widget easy to configure inside WPBakery.
You can see all types of fields for use in the WPBakery documentation.
Next, we need to render the generated shortcode. We registered the shortcode using the base parameter. Now we connect it to a handler function. We do this with the add_shortcode function. The first argument is the shortcode tag. The second argument is our render function. The render function will output the final HTML for the widget.
//render shortcode
add_shortcode( 'home_products', 'cxc_home_products_funct' );
function cxc_home_products_funct( $atts ) {
ob_start();
$atts = shortcode_atts(array(
'home_products_items' =>'', ), $atts, 'home_products');
$items = vc_param_group_parse_atts($atts['home_products_items']);
$width = (count($items) * 170) + (count($items) * 20) - 20;
?>
Our render function is named cxc_home_products_funct. It receives shortcode attributes in the $atts variable. We start output buffering using ob_start. This lets us capture HTML output. We set default attribute values using shortcode_atts. Then we parse the param group with vc_param_group_parse_atts. This converts the string values into an array of items.
We calculate the container width based on the item count. The width value controls horizontal layout. Then we output the main HTML structure. Each product item is inside a loop. For each item, we build the link using vc_build_link. We get the final URL from the link data. We render the image using wp_get_attachment_image. We also render the product title inside a link. After the loop, we close our HTML tags. Finally, we get the buffer content with ob_get_clean. The function returns this content as the shortcode output.
The final version of the code will be as follows:
//all code
add_action( 'vc_before_init', 'cxc_home_products_items_funct' );
function cxc_home_products_items_funct() {
vc_map(
array(
"name" => __("Home Products", "my-text-domain"), // Element name
"base" => "home_products", // Element shortcode
"class" => "home_products",
"category" => __('List Labs Components', 'my-text-domain'),
"icon" => "my_bartag",
'params' => array(
array(
'type' => 'param_group',
'param_name' => 'home_products_items',
'params' => array(
array(
"type" => "attach_image",
"holder" => "img",
"class" => "",
"heading" => __( "Image", "my-text-domain" ),
"param_name" => "home_products_items_img",
"value" => __( "", "my-text-domain" ),
),
array(
"type" => "textfield",
"holder" => "div",
"class" => "",
"heading" => __( "Title", "my-text-domain" ),
"param_name" => "home_products_items_title",
"value" => __( "", "my-text-domain" ),
),
array(
"type" => "vc_link",
"holder" => "a",
"class" => "",
"heading" => __( "Link", "my-text-domain" ),
"param_name" => "home_products_items_link",
"value" => __( "", "my-text-domain" ),
)
)
),
)
)
);
}
add_shortcode( 'home_products', 'cxc_home_products_funct' );
function cxc_home_products_funct( $atts ) {
ob_start();
$atts = shortcode_atts(array(
'home_products_items' =>'', ), $atts, 'home_products');
$items = vc_param_group_parse_atts($atts['home_products_items']);
$width = (count($items) * 170) + (count($items) * 20) - 20;
?>
We learned how to create a custom WPBakery widget. We covered the registration hook and widget settings. We also built a render function for the shortcode. This workflow can be reused for other custom components.
If you have any questions, feel free to contact us. We will be happy to help with your project. We can also assist with custom WPBakery or WordPress development. Just reach out and we will reply soon.