Skip to main content

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 + FilenamePurpose
d5-tutorial-simple-quick-module.phpPlugin registration, enqueueing visual builder script
/server/index.phpProvide the methods for rendering HTML, module classnames, module's styles and script-data output.
/visual-builder/package.jsonDefine node project's configuration.
/visual-builder/webpack.config.jsWe'll use webpack. It will allow us to write visual builder components using .jsx which makes the code more readable.
/visual-builder/src/index.jsxProvide 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.jsonModule 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:

Relationship between files on Divi 5 Module

How Module Works​

The very simplified version on how module works can be described into this:

  1. A json file called module.json file is used to keep module metadata that is needed for defining module settings and rendering module in Visual Builder and Frontend.
  2. In PHP, we need to:
  • Register the module
  • Provide the methods for rendering HTML, module classnames, module's styles and script-data output.
  1. 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:

Visual Builder vs Frontend Components Comparison

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:

/visual-builder/src/module.json
{
"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:

/visual-builder/src/index.jsx
// 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:

/visual-builder/webpack.config.js
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:

/visual-builder/package.json
{
"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/*
Windows Users

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

info

There are two commands available on node project settings:

  1. npm run build generates minified file for production.
  2. npm run start generates 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:

/server/index.php
<?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:

d5-tutorial-simple-quick-module.php
<?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​

Simple Quick Module Preview

note

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!