Create Divi 5 Module in 10 Minutes
The goal of this tutorial to help you to create your first Divi 5 module as quick as possible. The module will be simple but it will show you how to create Divi 5 module with full Visual Builder support.
Objective
The goal of this tutorial is to help you create your first Divi 5 module as quick as possible. It aims to showcase the potential of Divi 5 module: how simple (if you want to keep it simple) yet extendable (if you want to customize it further) it can be.
In this tutorial, we'll create a simple module that has title and content area. Imagine simplified version of Call To Action module:
* Module Wrapper ******
* Title *
* Content *
***********************
Prerequisites
You will need to have node and npm installed on your machine.
Familiarity with command line will also help.
Overview
Let's call this module Simple Quick Module. You will need to write 6 files to create an extension containing custom Divi 5 module with this functionality:
| Path + Filename | Purpose |
|---|---|
d5-tutorial-simple-quick-module.php | Plugin registration, enqueueing visual builder script |
/server/index.php | Provide the methods for rendering HTML, module classnames, module's styles and script-data output. |
/visual-builder/package.json | Define node project's configuration. |
/visual-builder/webpack.config.js | We'll use webpack. It will allow us to write visual builder components using .jsx which makes the code more readable. |
/visual-builder/src/index.jsx | Provide the functions/function component for rendering HTML, module classnames, module's styles, script-data output, and module settings' panels. More configuration such as placeholder content can be defined here as well. more on this later. |
/visual-builder/src/module.json | Module metadata that is used for defining the module settings and rendering the module on Visual Builder and Frontend. |
Note that we will write our visual builder components using .jsx because it is more readable and maintanable. We need to transpile the code into .js so that it can be read by browser. Thus, we will use node, npm, and ultimately webpack our source code into build js file that will be enqueued on browser. This build process will create the following files:
/visual-builder/package-lock.json
/visual-builder/node_modules/*
/visual-builder/build/d5-tutorial-simple-quick-module.js
If we visualize the relationship between the files above, it will look like this:

How Module Works
The very simplified version on how module works can be described into this:
- A json file called
module.jsonfile is used to keep module metadata that is needed for defining module settings and rendering module in Visual Builder and Frontend. - In PHP, we need to:
- Register the module
- Provide the methods for rendering HTML, module classnames, module's styles and script-data output.
- In Visual Builder (JS), we need:
- Register the module
- Provide the functions/function component for rendering HTML, module classnames, module's styles and script-data output
if we vsiualize these roles, it will look like this:

There are more things that can be done but considering the goal of this tutorial, this should be sufficient.
Write The Code
1. Create new plugin directory
Create new directory inside wp-content/plugins and name it d5-tutorial-simple-quick-module. This will be our plugin dir. From now on, the path described below is relative to this plugin directory.
2. Declare module metadata
How to do it:
{
"name": "d5-tut/simple-quick-module",
"d4Shortcode": "d5_tut_simple_quick_module",
"title": "Simple Quick Module",
"titles": "Simple Quick Modules",
"category": "module",
"moduleClassName": "d5_tut_simple_quick_module",
"moduleOrderClassName": "d5_tut_simple_quick_module",
"attributes": {
"module": {
"type": "object",
"selector": "{{selector}}",
"default": {
"meta": {
"adminLabel": {
"desktop": {
"value": "Simple Quick Module"
}
}
}
},
"settings": {
"meta": {
"adminLabel": {}
},
"decoration": {
"layout": {},
"background": {},
"sizing": {},
"spacing": {},
"border": {},
"boxShadow": {},
"filters": {},
"transform": {},
"animation": {},
"overflow": {},
"disabledOn": {},
"transition": {},
"position": {},
"zIndex": {},
"scroll": {},
"sticky": {}
}
}
},
"title": {
"type": "object",
"selector": "{{selector}} .d5_tut_simple_quick_module_title",
"attributes": {
"class": "d5_tut_simple_quick_module_title"
},
"tagName": "h2",
"inlineEditor": "plainText",
"elementType": "heading",
"childrenSanitizer": "et_core_esc_previously",
"settings": {
"innerContent": {
"groupType": "group-item",
"item": {
"groupName": "mainContent",
"priority": 10,
"render": true,
"attrName": "title.innerContent",
"label": "Title",
"description": "Title",
"features": {
"dynamicContent": false
},
"component": {
"name": "divi/text",
"type": "field"
}
}
},
"decoration": {
"layout": {},
"font": {
"priority": 10,
"component": {
"props": {
"groupLabel": "Title Font",
"fieldLabel": "",
"fields": {
"headingLevel": {
"render": false
}
}
}
}
}
}
}
},
"content": {
"type": "object",
"selector": "{{selector}} .d5_tut_simple_quick_module_content",
"attributes": {
"class": "d5_tut_simple_quick_module_content"
},
"tagName": "div",
"inlineEditor": "richText",
"childrenSanitizer": "et_core_esc_previously",
"allowHtml": true,
"settings": {
"innerContent": {
"groupType": "group-item",
"item": {
"groupName": "mainContent",
"priority": 20,
"render": true,
"attrName": "content.innerContent",
"label": "Content",
"description": "Content",
"features": {
"dynamicContent": false
},
"component": {
"name": "divi/richtext",
"type": "field"
}
}
},
"decoration": {
"layout": {},
"bodyFont": {}
}
}
}
},
"settings": {
"content": "auto",
"design": "auto",
"advanced": "auto"
}
}
3. Visual Builder's layout and module settings component
How to do it:
// External library dependencies.
import React from 'react';
// WordPress package dependencies.
const {
addAction,
} = window?.vendor?.wp?.hooks;
// Divi package dependencies.
const {
// Renderer - HTML
ModuleContainer,
// Renderer - Styles
StyleContainer,
// Renderer - Classnames
elementClassnames,
} = window?.divi?.module;
const {
registerModule
} = window?.divi?.moduleLibrary;
// Module metadata that is used in both Frontend and Visual Builder.
import metadata from './module.json';
/**
* React function component for rendering module style.
*/
const ModuleStyles = ({
attrs,
elements,
settings,
orderClass,
mode,
state,
noStyleTag
}) => (
<StyleContainer mode={mode} state={state} noStyleTag={noStyleTag}>
{/* Element: Module */}
{elements.style({
attrName: 'module',
styleProps: {
disabledOn: {
disabledModuleVisibility: settings?.disabledModuleVisibility
}
}
})}
{/* Element: Title */}
{elements.style({
attrName: 'title',
})}
{/* Element: Content */}
{elements.style({
attrName: 'content',
})}
</StyleContainer>
);
/**
* React function component for registering module script data.
*/
const ModuleScriptData = ({
elements,
}) => (
<React.Fragment>
{elements.scriptData({
attrName: 'module',
})}
</React.Fragment>
);
/**
* Function for registering module classnames.
*/
const moduleClassnames = ({
classnamesInstance,
attrs,
}) => {
// Add element classnames.
classnamesInstance.add(
elementClassnames({
attrs: attrs?.module?.decoration ?? {},
}),
);
}
/**
* Simple Quick Module.
*/
const simpleQuickModule = {
// Metadata that is used on Visual Builder and Frontend
metadata,
// Layout renderer components.
renderers: {
// React Function Component for rendering module's output on layout area.
edit: ({
attrs,
id,
name,
elements,
}) => (
<ModuleContainer
attrs={attrs}
elements={elements}
id={id}
moduleClassName="d5_tut_simple_quick_module"
name={name}
scriptDataComponent={ModuleScriptData}
stylesComponent={ModuleStyles}
classnamesFunction={moduleClassnames}
>
{elements.styleComponents({
attrName: 'module',
})}
<div className="et_pb_module_inner">
{elements.render({
attrName: 'title',
})}
{elements.render({
attrName: 'content',
})}
</div>
</ModuleContainer>
),
},
// Attribute that is automatically added into the module when the module is inserted
// into the layout so that the newly inserted module has some placeholder content
placeholderContent: {
module: {
decoration: {
background: {
desktop: {
value: {
color: '#DFDFDF',
}
}
},
}
},
title: {
innerContent: {
desktop: {
value: 'Module Title'
}
}
},
content: {
innerContent: {
desktop: {
value: 'Module Content'
}
}
}
},
};
// Register module.
addAction('divi.moduleLibrary.registerModuleLibraryStore.after', 'd5Tut.simpleQuickModule', () => {
registerModule(simpleQuickModule.metadata, simpleQuickModule);
});
4. Webpack configuration
To be able to transpile the JSX file into JS build output, do:
const path = require('path');
module.exports = {
// Webpack starts bundling the assets from the following file.
// @see https://webpack.js.org/concepts/#entry
entry: {
bundle: './src/index.jsx',
},
// Divi Visual Builder use of scripts that is already enqueued by WordPress and available
// in global scope so those scripts don't need to be included on the bundle. For webpack
// to recognize those files, the global variable needs to be registered as externals.
// These allows global variable listed below to be imported into the module.
// @see https://webpack.js.org/configuration/externals/#externals
externals: {
// Third party dependencies.
react: 'React',
},
// This option determine how different types of module within the project will be treated.
// @see https://webpack.js.org/configuration/module/
module: {
// This option sets up loaders for webpack configuration.
// Loaders allow webpack to process various types because by default webpack only
// understand JavaScript and JSON files.
// @see https://webpack.js.org/concepts/#loaders
rules: [
// Handle `.jsx` files.
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: [
// Spawns multiple processes and split work between them. This makes faster build.
// @see https://webpack.js.org/loaders/thread-loader/
{
loader: 'thread-loader',
options: {
workers: - 1,
},
},
// Transpiles JavaScript files using Babel. Translates newer syntax with less support
// into older syntax with more support so the project can use newer syntax and have
// them automatically translated into older syntax for compatibility suppoert.
// @see https://www.npmjs.com/package/babel-loader
// @see https://babeljs.io/
{
loader: 'babel-loader',
options: {
compact: false,
presets: [
// Preset that adds configuration for handling latest JavaScript syntax.
// @see https://babeljs.io/docs/en/babel-preset-env
['@babel/preset-env', {
modules: false,
targets: '> 5%',
}],
// Preset that added configuration for handling react & JSX.
// @see https://babeljs.io/docs/en/babel-preset-react
'@babel/preset-react',
],
cacheDirectory: false,
},
}
]
},
]
},
// Determine how modules are resolved.
// @see https://webpack.js.org/configuration/resolve/
resolve: {
// Allows extension to be leave off when importing.
// @see https://webpack.js.org/configuration/resolve/#resolveextensions
extensions: ['.js', '.jsx'],
},
// Determine where the created bundles will be outputted.
// @see https://webpack.js.org/concepts/#output
output: {
filename: 'd5-tutorial-simple-quick-module.js',
path: path.resolve(__dirname, 'build'),
},
};
5. Node project settings
To be able to run webpack, create the following package.json to set up node project:
{
"name": "d5-tutorial-simple-quick-module",
"version": "1.0.0",
"description": "D5 Tutorial for module conversion from Divi 4 to Divi 5",
"main": "src/index.jsx",
"author": "Elegant Themes",
"license": "GPL2",
"private": false,
"devDependencies": {
"@babel/core": "^7.21.0",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"babel-loader": "^9.1.2",
"thread-loader": "^3.0.4",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1"
},
"scripts": {
"start": "NODE_ENV=development webpack -w --config webpack.config.js --progress",
"build": "NODE_ENV=production webpack --config webpack.config.js --progress"
}
}
6. Setup node and create build output
Go to /visual-builder directory then install the dependencies:
cd visual-builder
npm install
This process will generate these two files:
/visual-builder/package-lock.json
/visual-builder/node_modules/*
If you're on Windows, you may encounter an error: 'NODE_ENV' is not recognized as an internal or external command. To fix this, install the win-node-env package:
npm install --save-optional win-node-env
Then run the build command again. This package enables Windows to recognize the NODE_ENV environment variable used in the npm scripts.
Once it is completed, create the build output:
npm run build
This will generate the build output located at:
/visual-builder/build/d5-tutorial-simple-quick-module.js
There are two commands available on node project settings:
npm run buildgenerates minified file for production.npm run startgenerates unminified file for development and watch for the changes. When change occurs, it will automatically recompile the file.
7. Frontend renderer
We're done with Visual Builder part; Now we are creating the frontend renderer:
<?php
namespace D5TUTSimpleQuickModule;
if ( ! defined( 'ABSPATH' ) ) {
die( 'Direct access forbidden.' );
}
require_once ABSPATH . 'wp-content/themes/Divi/includes/builder-5/server/Framework/DependencyManagement/Interfaces/DependencyInterface.php';
use ET\Builder\Framework\DependencyManagement\Interfaces\DependencyInterface;
use ET\Builder\Framework\Utility\HTMLUtility;
use ET\Builder\FrontEnd\Module\Style;
use ET\Builder\Packages\Module\Module;
use ET\Builder\Packages\Module\Options\Element\ElementClassnames;
use ET\Builder\Packages\ModuleLibrary\ModuleRegistration;
/**
* Class that handle "Simple Quick Module" module output in frontend.
*/
class D5TutorialSimpleQuickModule implements DependencyInterface {
/**
* Register module.
* `DependencyInterface` interface ensures class method name `load()` is executed for initialization.
*/
public function load() {
// Register module.
add_action( 'init', [ D5TutorialSimpleQuickModule::class, 'register_module' ] );
}
/**
* Register module.
*/
public static function register_module() {
// Path to module metadata that is shared between Frontend and Visual Builder.
$module_json_folder_path = dirname( __DIR__, 1 ) . '/visual-builder/src';
ModuleRegistration::register_module(
$module_json_folder_path,
[
'render_callback' => [ D5TutorialSimpleQuickModule::class, 'render_callback' ],
]
);
}
/**
* Render module style.
*/
public static function module_styles( $args ) {
$attrs = $args['attrs'] ?? [];
$elements = $args['elements'];
Style::add(
[
'id' => $args['id'],
'name' => $args['name'],
'orderIndex' => $args['orderIndex'],
'storeInstance' => $args['storeInstance'],
'styles' => [
// Module.
$elements->style(
[
'attrName' => 'module',
'styleProps' => [
'disabledOn' => [
'disabledModuleVisibility' => $args['settings']['disabledModuleVisibility'] ?? null,
],
],
]
),
// Title.
$elements->style(
[
'attrName' => 'title',
]
),
// Content.
$elements->style(
[
'attrName' => 'content',
]
),
],
]
);
}
/**
* Render module script data.
*/
public static function module_script_data( $args ) {
$elements = $args['elements'];
// Element Script Data Options.
$elements->script_data(
[
'attrName' => 'module',
]
);
}
/**
* Render module classnames.
*/
public static function module_classnames( $args ) {
$classnames_instance = $args['classnamesInstance'];
$attrs = $args['attrs'];
// Module.
$classnames_instance->add(
ElementClassnames::classnames(
[
'attrs' => $attrs['module']['decoration'] ?? [],
]
)
);
}
/**
* Render module HTML output.
*/
public static function render_callback( $attrs, $content, $block, $elements ) {
// Title.
$title = $elements->render(
[
'attrName' => 'title',
]
);
// Content.
$content = $elements->render(
[
'attrName' => 'content',
]
);
// Module Inner.
// Essentially, this is the module content.
// Were wrapping the title and content in a div with class `et_pb_module_inner`.
$module_inner = HTMLUtility::render(
[
'tag' => 'div',
'attributes' => [
'class' => 'et_pb_module_inner',
],
'childrenSanitizer' => 'et_core_esc_previously',
'children' => $title . $content,
]
);
// This are the module elements that will be rendered in the frontend.
$module_elements = $elements->style_components(
[
'attrName' => 'module',
]
);
// This are the children of the module container, which are the module elements and the module inner.
$module_container_children = $module_elements . $module_inner;
return Module::render(
[
// FE only.
'orderIndex' => $block->parsed_block['orderIndex'],
'storeInstance' => $block->parsed_block['storeInstance'],
// VB equivalent.
'attrs' => $attrs,
'elements' => $elements,
'id' => $block->parsed_block['id'],
'moduleClassName' => 'd5_tut_simple_quick_module',
'name' => $block->block_type->name,
'classnamesFunction' => [ D5TutorialSimpleQuickModule::class, 'module_classnames' ],
'moduleCategory' => $block->block_type->category,
'stylesComponent' => [ D5TutorialSimpleQuickModule::class, 'module_styles' ],
'scriptDataComponent' => [ D5TutorialSimpleQuickModule::class, 'module_script_data' ],
'children' => $module_container_children,
]
);
}
}
// Register module.
add_action(
'divi_module_library_modules_dependency_tree',
function( $dependency_tree ) {
$dependency_tree->add_dependency( new D5TutorialSimpleQuickModule() );
}
);
8. Plugin registration + enqueue script
Module components are now on their place. The last step is to register the plugin and enqueue the visual builder script:
<?php
/*
Plugin Name: Divi 5 Tutorial Simple Quick Module
Plugin URI:
Description: Plugin reference for creating simple and quick Divi 5 Module.
Version: 1.0.0
Author: Elegant Themes
Author URI: https://elegantthemes.com
License: GPL2
License URI: https://www.gnu.org/licenses/gpl-2.0.html
*/
if ( ! defined( 'ABSPATH' ) ) {
die( 'Direct access forbidden.' );
}
// Setup constants.
define( 'D5_TUTORIAL_SIMPLE_QUICK_MODULE_PATH', plugin_dir_path( __FILE__ ) );
define( 'D5_TUTORIAL_SIMPLE_QUICK_MODULE_URL', plugin_dir_url( __FILE__ ) );
// Load Divi 5 modules.
require_once D5_TUTORIAL_SIMPLE_QUICK_MODULE_PATH . 'server/index.php';
/**
* Enqueue Divi 5 Visual Builder Assets
*/
function d5_tutorial_simple_quick_module_enqueue_visual_builder_assets() {
if ( et_core_is_fb_enabled() && et_builder_d5_enabled() ) {
\ET\Builder\VisualBuilder\Assets\PackageBuildManager::register_package_build(
[
'name' => 'd5-tutorial-simple-quick-module-visual-builder',
'version' => '1.0.0',
'script' => [
'src' => D5_TUTORIAL_SIMPLE_QUICK_MODULE_URL . 'visual-builder/build/d5-tutorial-simple-quick-module.js',
'deps' => [
'react',
'jquery',
'divi-module-library',
'wp-hooks',
'divi-rest',
],
'enqueue_top_window' => false,
'enqueue_app_window' => true,
],
]
);
}
}
add_action( 'divi_visual_builder_assets_before_enqueue_scripts', 'd5_tutorial_simple_quick_module_enqueue_visual_builder_assets' );
Activating Extension
The plugin is ready. Go to Dashboard > Plugins and activates Divi 5 Tutorial Simple Quick Module.
Now you can go to visual builder and try adding new module called Simple Quick Module.
Preview

Note that not all possible functionality are being added on this tutorial. Certain features that required further explanation (eg. Custom CSS Field) are intentionally omitted.
Got a question or feedback? Ask away!