Adding custom modules to Divi

Since writing this tutorial almost two years ago, a lot has changed and adding modules to Divi is, you’ll be glad to hear, much easier than it was once upon a time. This post has been updated to reflect what it takes to add modules to Divi 3.0 and upwards. This is a little bit more advanced than our average tutorial/article. Proceed with caution and enthusiasm 🙂 If you were referring back to this article for the ‘old way’ to do this, don’t worry, it’s down there. Just keep scrolling.

I’ve been making major customisations to Divi for the better part of three years, for personal projects and Divi related products. A major part of that (particularly recently) has been creating new modules and adding them to the Divi builder so they can be used alongside the 40 or so modules that ship with Divi. Adding modules was not always an enjoyable experience. In the early days it involved a lot of trial and error, reverse engineering of Divi and a little luck to get something working. Why? Because there was no API or official instructions from Elegant Themes on how to include new modules into Divi’s interface. That’s no criticism. Divi’s success was astronomic and the growth of the third party development ecosystem caught everybody by surprise. As developers, we didn’t have official support then. We do now.

The Divi Developer API

As of Divi version 3.1, we have not only a Divi Developer API (Yay!), but also official instructions on how to use it to put together well developed, lightweight and fully integrated custom modules. This isn’t just good news for developers. Modules built within the new guidelines provide a conflict free, intuitive experience for the end user, working as expected, even in the new front end visual builder. They feel like they belong there and they share many of the settings and controls with default modules, providing a user experience in line with default modules. So before we discuss what goes into a custom module, let’s look at exactly why this new system is better.

Old Custom Modules New Custom Modules
Extends the array of modules available to the end user on the back end.
Extends the array of modules available to the end user on the front end.
Can be customized using the old back end builder.
Can be customized using the new Visual Builder.
Follows Elegant Themes development guidelines.

As you can see, the new API is a godsend for anyone looking to extend Divi. With that said, let’s take a look at how we get started.

Previously when working with custom modules, we were adding files to a child theme. The new way sees us working inside of a ‘functionality plugin’ that includes all of the files we need to add ‘visual builder compatible’ modules. So what goes into the plugin? Well actually, quite a lot.

Here’s the file tree that comes in a basic ‘Divi Module’ plugin.

plugin-folder
├── includes
│   ├── modules
│   │ └── HelloWorld
│   │    ├── HelloWorld.jsx
│   │    ├── HelloWorld.php
│   │    └── style.css
│   ├── loader.js
│   ├── loader.php
│   └── MyExtension.php
├── languages
├── node_modules
├── scripts
│   └── frontend.js
├── styles
├── module-extension.php
├── package.json
└── README.md

Looking at this extensive list can be a bit daunting. Don’t worry, most of these files exist to aid in the development of a custom Divi module, but don’t provide any functionality for the end user. You’re not going to be required to write (or even touch) most of the files included here, because Elegant Themes have created a command line utility that does that for you. Your code is going to live in the files in bold.

A module consists of a php, jsx and css file. The easiest way to think of this is that php is the back end settings, jsx is the frontend builder compatibility and css is module styling.

The Create Divi Extension

The Create Divi extension is a command line utility based on the Facebook backed Create React App. Essentially, it is an ‘installable-through-the-command-line’ local environment for creating Divi modules. It includes everything you need to create custom modules, including Webpack, ESlint and Babel, but don’t worry if you don’t know too much about what those things are, because the Create Divi Extension acts as a layer over all of those code libraries so you can harness the power of them without having to install or set them up directly.

There are only a few dependencies and other tools you need to install before you can get started and they are listed below.

Node.js is a powerful runtime application that is required to build custom Divi modules using the Create Divi Extension. Depending on what local environment you are using to build local sites, you may have some version of node.js installed on your system already. You should still go straight to nodejs.org and download the full version to ensure you have the right packages.

Yarn is a package manager that Elegant Themes recommend having installed before running the Create Divi Extension. I’m not overly sure what it adds to package management above what’s included in Node.js but if Elegant Themes recommend it, that’s good enough for me.

Sounds obvious, but please make sure you have Divi installed and activated before you attempt to add things to it 🙂

This is where you’ll need Divi installed and where you’ll be using the Create Divi Extension to generate a Divi Module Plugin.

Getting Started With The Create Divi Extension

Assuming you’ve installed the above listed software properly, you’ll know want to find the file path to your plugins folder in your local install. I’m working on a Windows PC running Flywheel Local so for me I can just navigate to the plugin folder from the Flywheel Local Site Screen and then hold shift + alt + right click and then choose Copy as path from the context menu.

  • Google+
  • Facebook
  • Twitter

Consult your OS and Local Environment documentation if your setup differs from mine.

Next you need to open up whichever command line utility your system uses. For me it is Command Prompt where the magic happens. Be sure to run as an administrator by right clicking on the application name and choosing ‘run as administrator’.

Here you want to type cd and then paste your file path after it, this will open up the plugins directory:

  • Google+
  • Facebook
  • Twitter

On the new line, you want to type npx create-divi-extension new-module-demo

If you have installed your dependencies correctly, a whole bunch of magic stuff is going to happen as the Divi Create Extension installs everything you need to get started building modules. This can take a few minutes. During this process you’ll be prompted to add some information about your new extension including name and description. If you’ve done everything successfully this’ll be followed by a success message.

  • Google+
  • Facebook
  • Twitter

From here you’ll want to change directory (cd) again into your new module by typing cd new-module-demo.

The plugin as it currently stands is still not built to work on its own so you’ll have to type yarn start at this point to compile it before you can use it. If you are using Flywheel Local, you’ll also have to update your plugin’s class to overcome a compatibility issue. You can find details of how to that and why, here.

It’s important to remember that if you are working on your module then you need to keep the window with yarn start running open the whole time. Minimise it, but don’t close it. This tripped me up a bunch of times when I first started. Check back on that window regularly because if you make a mistake when editing your module, this is where it will tell you.

If you’ve carried out those steps correctly then you can activate your module plugin and you’ll have an example module named HelloWorld that works correctly on the front end builder. Magic!

This is a very basic example including a tinymce powered text editor, but it should give you enough to see how the build works and to also have a go at customizing it. If you want your plugin to work in a standalone environment then you’ll have to build (yarn build) and zip (yarn zip) it with yarn by using the aforementioned commands in the same directory as you did for yarn start.

If you want to manually edit your plugin and get rid of the dev environment then you can use the command yarn eject, but be careful, because once you do, there’s no going back.

If you would like to customize your module further, Elegant Themes has a whole series on creating custom modules in their developer documentation. I would encourage you to go through it as it really is an awesome step by step 🙂

The Divi Module Creator’s Course

  • Google+
  • Facebook
  • Twitter

If you’re absolutely loving the idea of creating highly functional modules for Divi, then I have a course on exactly that. Designed to teach you how to extend the Divi framework, the Divi Module Creators Course will equip learners with the skills and knowledge to utilize the Divi Create Extension that we’ve covered here briefly, in new and exciting ways.

The Divi Module Creators Course will give learners a deep understanding of the Divi theme framework, it’s modules and how to develop custom modules that satisfy both aesthetic and functional purposes.

You can learn more here.

More JSX and PHP Settings

The simple text field that comes as the example of a module input is great but if you’d like to check out some other examples, Elegant Themes have come to the rescue again:

You can check out their pre-built module examples including several complex setups at the create divi extension example github repo here.

You can also use these basic examples we have put together for you, by replacing the relevant bits of code in your module php and jsx files.

PHP

...

public function get_fields() {
 return array(
 'setting_1' => array(
 'label' => esc_html__( 'Setting 1', 'module-slug' ),
 'type' => 'tiny_mce',
 'option_category' => 'basic_option',
 'description' => esc_html__( 'Content entered here will appear inside the module.', 'nemo-new-module' ),
 'toggle_slug' => 'main_content',
 ),
 'setting_2' => array(
'label' => esc_html__( 'Textarea Field', 'nemo-new-module' ),
'type' => 'textarea',
'option_category' => 'basic_option',
'description' => esc_html__( 'Description for Textarea field', 'nemo-new-module' ),
'toggle_slug' => 'main_content',
),
'setting_3' => array(
'label' => esc_html__( 'Text Field', 'nemo-new-module' ),
'type' => 'text',
'option_category' => 'basic_option',
'description' => esc_html__( 'Description for text field', 'nemo-new-module' ),
'toggle_slug' => 'main_content',
),
'setting_4' => array(
'label' => esc_html__( 'Yes No Field', 'nemo-new-module' ),
'type' => 'yes_no_button',
'options' => array(
'no' => esc_html('No','nemo-new-module'),
'yes' => esc_html('Yes','nemo-new-module'),
),
'option_category' => 'basic_option',
'description' => esc_html__( 'Description for yes no field', 'nemo-new-module' ),
'toggle_slug' => 'main_content',
),
'setting_5' => array(
'label' => esc_html__( 'Image Upload', 'nemo-new-module' ),
'type' => 'upload',
'option_category' => 'basic_option',
'choose_text' => esc_attr__('Choose an image','nemo-new-module'),
'upload_button_text' => esc_attr__('Upload an image','nemo-new-module'),
'description' => esc_html__( 'Upload an image ', 'nemo-new-module' ),
'data_type' => 'image',
'toggle_slug' => 'main_content',
),
'setting_6' => array(
'label' => esc_html__( 'Range Slider', 'nemo-new-module' ),
'type' => 'range',
'range_settings' => array(
 'min' => '50px',
 'max' => '200px',
 'step' => '10px',
),
'fixed_unit' => 'px',
'default' => '100px',
'option_category' => 'basic_option',
'description' => esc_html__( 'Description for Range Slider', 'nemo-new-module' ),
'toggle_slug' => 'main_content',
),
'setting_7' => array(
'label' => esc_html__( 'Color Field', 'nemo-new-module' ),
'type' => 'color',
'default' => '#000000',
'option_category' => 'basic_option',
'description' => esc_html__( 'Description for text field', 'nemo-new-module' ),
'toggle_slug' => 'main_content',
),

 );
}

public function render( $attrs, $content = null, $render_slug ) {
 return sprintf('
 <div>
 <h1>%1$s</h1>
 <p>%2$s</p> 
<p>%3$s</p> 
<p class="%4$s">The class for this text changes on yes/no toggle</p> 
<img alt="change-this" src="%5$s"/>
<p>%6$s</p> 
<p>The code for this color is: %7$s</p> 
 
 </div>', 
 $this->props['setting_1'], $this->props['setting_2'], $this->props['setting_3'], $this->props['setting_4'], $this->props['setting_5'], $this->props['setting_6'], $this->props['setting_7'] 
 );
}

...

JSX

...

render() {

 return (
 <div>
 <h1>{this.props.setting_1}</h1>
 <p>{this.props.setting_2}</p> 
<p>{this.props.setting_3}</p> 
<p className={this.props.setting_4}>The class for this text changes on yes/no toggle</p> 
<img alt="change-this" src={this.props.setting_5}/>
<p>{this.props.setting_6}</p> 
<p>The code for this color is: {this.props.setting_7}</p> 

 </div>
 );

}

...

If you’re not building modules yet, I hope this post helps you get started. If you are then please share them in the comments so we can see what you’re up to ! 🙂


Custom Divi Modules: The Old Way

Were you looking for the old version of this post? Everything that follows is the old way to build custom modules.

Whilst helping out in my favourite Divi theme group recently a case came up for making small structural changes to a Divi page builder module. In this case, we needed to place the grid blog module header above the featured image, like so:

modified blog module
  • Google+
  • Facebook
  • Twitter

It occurred to me that others may be interested in editing modules in this way, so here’s how you do it.

Extending the page builder

At this point we’re assuming you have a child theme with its own functions.php set up. Add this code to your functions.php to extend the page builder module to a new file that we’re going to add to our child theme called ‘ds-custom-modules.php’…

function DS_Custom_Modules(){
 if(class_exists("ET_Builder_Module")){
 include("ds-custom-modules.php");
 }
}

function Prep_DS_Custom_Modules(){
 global $pagenow;

$is_admin = is_admin();
 $action_hook = $is_admin ? 'wp_loaded' : 'wp';
 $required_admin_pages = array( 'edit.php', 'post.php', 'post-new.php', 'admin.php', 'customize.php', 'edit-tags.php', 'admin-ajax.php', 'export.php' ); // list of admin pages where we need to load builder files
 $specific_filter_pages = array( 'edit.php', 'admin.php', 'edit-tags.php' );
 $is_edit_library_page = 'edit.php' === $pagenow && isset( $_GET['post_type'] ) && 'et_pb_layout' === $_GET['post_type'];
 $is_role_editor_page = 'admin.php' === $pagenow && isset( $_GET['page'] ) && 'et_divi_role_editor' === $_GET['page'];
 $is_import_page = 'admin.php' === $pagenow && isset( $_GET['import'] ) && 'wordpress' === $_GET['import']; 
 $is_edit_layout_category_page = 'edit-tags.php' === $pagenow && isset( $_GET['taxonomy'] ) && 'layout_category' === $_GET['taxonomy'];

if ( ! $is_admin || ( $is_admin && in_array( $pagenow, $required_admin_pages ) && ( ! in_array( $pagenow, $specific_filter_pages ) || $is_edit_library_page || $is_role_editor_page || $is_edit_layout_category_page || $is_import_page ) ) ) {
 add_action($action_hook, 'DS_Custom_Modules', 9789);
 }
}
Prep_DS_Custom_Modules();

The next step is to create the file called ds-custom-modules.php and add it to our child theme root alongside functions.php and any other files we have in there.

Housing new modules

Now the next part depends on which module you’d like to edit. In the Divi theme files includes > builder > main-modules.php you’ll find all of the modules. In our case we wanted to edit the blog module so we copied everything between these two lines –

class ET_Builder_Module_Blog extends ET_Builder_Module {
EVERYTHING IN HERE GOT COPIED
new ET_Builder_Module_Blog;

Each module will start and end like this so it’s easy to know what you need to copy. There are three things that you must change in your new blog module: The class, the name and the slug. You’ll find those lines at the top of your module –

class DS_Custom_Module_Blog extends ET_Builder_Module {
 function init() {
 $this->name = esc_html__( 'Blog - Custom Grid', 'et_builder' );
 $this->slug = 'et_pb_blog_2';

You can see in our example we kept it simple and renamed it ‘Blog – Custom Grid’ and changed the slug to ‘et_pb_blog_2’, but you can change it to whatever you want to. We renamed our class to ‘DS_Custom_Module_blog’.

As you can see, the new module now appears alongside our normal ones:

The only other changes you should need to make is to adjust the structure or design as needed. If you plan on making CSS changes then you may want to add new classes to the various elements so you can target them separately to the standard modules, but that CSS can then be added in your style sheet as normal.

For reference and testing, here is the code from our custom module –

<?php

class DS_Custom_Module_Blog extends ET_Builder_Module {
 function init() {
 $this->name = esc_html__( 'Blog - Custom Grid', 'et_builder' );
 $this->slug = 'et_pb_blog_2';

 $this->whitelisted_fields = array(
 'fullwidth',
 'posts_number',
 'include_categories',
 'meta_date',
 'show_thumbnail',
 'show_content',
 'show_more',
 'show_author',
 'show_date',
 'show_categories',
 'show_comments',
 'show_pagination',
 'offset_number',
 'background_layout',
 'admin_label',
 'module_id',
 'module_class',
 'masonry_tile_background_color',
 'use_dropshadow',
 'use_overlay',
 'overlay_icon_color',
 'hover_overlay_color',
 'hover_icon',
 );

 $this->fields_defaults = array(
 'fullwidth' => array( 'off' ),
 'posts_number' => array( 10, 'add_default_setting' ),
 'meta_date' => array( 'M j, Y', 'add_default_setting' ),
 'show_thumbnail' => array( 'on' ),
 'show_content' => array( 'off' ),
 'show_more' => array( 'off' ),
 'show_author' => array( 'on' ),
 'show_date' => array( 'on' ),
 'show_categories' => array( 'on' ),
 'show_comments' => array( 'off' ),
 'show_pagination' => array( 'on' ),
 'offset_number' => array( 0, 'only_default_setting' ),
 'background_layout' => array( 'light' ),
 'use_dropshadow' => array( 'off' ),
 'use_overlay' => array( 'off' ),
 );

 $this->main_css_element = '%%order_class%% .et_pb_post';
 $this->advanced_options = array(
 'fonts' => array(
 'header' => array(
 'label' => esc_html__( 'Header', 'et_builder' ),
 'css' => array(
 'main' => "{$this->main_css_element} h2",
 'important' => 'all',
 ),
 ),
 'meta' => array(
 'label' => esc_html__( 'Meta', 'et_builder' ),
 'css' => array(
 'main' => "{$this->main_css_element} .post-meta",
 ),
 ),
 'body' => array(
 'label' => esc_html__( 'Body', 'et_builder' ),
 'css' => array(
 'line_height' => "{$this->main_css_element} p",
 ),
 ),
 ),
 'border' => array(),
 );
 $this->custom_css_options = array(
 'title' => array(
 'label' => esc_html__( 'Title', 'et_builder' ),
 'selector' => '.et_pb_post h2',
 ),
 'post_meta' => array(
 'label' => esc_html__( 'Post Meta', 'et_builder' ),
 'selector' => '.et_pb_post .post-meta',
 ),
 'pagenavi' => array(
 'label' => esc_html__( 'Pagenavi', 'et_builder' ),
 'selector' => '.wp_pagenavi',
 ),
 'featured_image' => array(
 'label' => esc_html__( 'Featured Image', 'et_builder' ),
 'selector' => '.et_pb_image_container',
 ),
 'read_more' => array(
 'label' => esc_html__( 'Read More Button', 'et_builder' ),
 'selector' => '.et_pb_post .more-link',
 ),
 );
 }

 function get_fields() {
 $fields = array(
 'posts_number' => array(
 'label' => esc_html__( 'Posts Number', 'et_builder' ),
 'type' => 'text',
 'option_category' => 'configuration',
 'description' => esc_html__( 'Choose how much posts you would like to display per page.', 'et_builder' ),
 ),
 'include_categories' => array(
 'label' => esc_html__( 'Include Categories', 'et_builder' ),
 'renderer' => 'et_builder_include_categories_option',
 'option_category' => 'basic_option',
 'renderer_options' => array(
 'use_terms' => false,
 ),
 'description' => esc_html__( 'Choose which categories you would like to include in the feed.', 'et_builder' ),
 ),
 'meta_date' => array(
 'label' => esc_html__( 'Meta Date Format', 'et_builder' ),
 'type' => 'text',
 'option_category' => 'configuration',
 'description' => esc_html__( 'If you would like to adjust the date format, input the appropriate PHP date format here.', 'et_builder' ),
 ),
 'show_thumbnail' => array(
 'label' => esc_html__( 'Show Featured Image', 'et_builder' ),
 'type' => 'yes_no_button',
 'option_category' => 'configuration',
 'options' => array(
 'on' => esc_html__( 'Yes', 'et_builder' ),
 'off' => esc_html__( 'No', 'et_builder' ),
 ),
 'description' => esc_html__( 'This will turn thumbnails on and off.', 'et_builder' ),
 ),
 'show_content' => array(
 'label' => esc_html__( 'Content', 'et_builder' ),
 'type' => 'select',
 'option_category' => 'configuration',
 'options' => array(
 'off' => esc_html__( 'Show Excerpt', 'et_builder' ),
 'on' => esc_html__( 'Show Content', 'et_builder' ),
 ),
 'affects' => array(
 '#et_pb_show_more',
 ),
 'description' => esc_html__( 'Showing the full content will not truncate your posts on the index page. Showing the excerpt will only display your excerpt text.', 'et_builder' ),
 ),
 'show_more' => array(
 'label' => esc_html__( 'Read More Button', 'et_builder' ),
 'type' => 'yes_no_button',
 'option_category' => 'configuration',
 'options' => array(
 'off' => esc_html__( 'Off', 'et_builder' ),
 'on' => esc_html__( 'On', 'et_builder' ),
 ),
 'depends_show_if' => 'off',
 'description' => esc_html__( 'Here you can define whether to show "read more" link after the excerpts or not.', 'et_builder' ),
 ),
 'show_author' => array(
 'label' => esc_html__( 'Show Author', 'et_builder' ),
 'type' => 'yes_no_button',
 'option_category' => 'configuration',
 'options' => array(
 'on' => esc_html__( 'Yes', 'et_builder' ),
 'off' => esc_html__( 'No', 'et_builder' ),
 ),
 'description' => esc_html__( 'Turn on or off the author link.', 'et_builder' ),
 ),
 'show_date' => array(
 'label' => esc_html__( 'Show Date', 'et_builder' ),
 'type' => 'yes_no_button',
 'option_category' => 'configuration',
 'options' => array(
 'on' => esc_html__( 'Yes', 'et_builder' ),
 'off' => esc_html__( 'No', 'et_builder' ),
 ),
 'description' => esc_html__( 'Turn the date on or off.', 'et_builder' ),
 ),
 'show_categories' => array(
 'label' => esc_html__( 'Show Categories', 'et_builder' ),
 'type' => 'yes_no_button',
 'option_category' => 'configuration',
 'options' => array(
 'on' => esc_html__( 'Yes', 'et_builder' ),
 'off' => esc_html__( 'No', 'et_builder' ),
 ),
 'description' => esc_html__( 'Turn the category links on or off.', 'et_builder' ),
 ),
 'show_comments' => array(
 'label' => esc_html__( 'Show Comment Count', 'et_builder' ),
 'type' => 'yes_no_button',
 'option_category' => 'configuration',
 'options' => array(
 'on' => esc_html__( 'Yes', 'et_builder' ),
 'off' => esc_html__( 'No', 'et_builder' ),
 ),
 'description' => esc_html__( 'Turn comment count on and off.', 'et_builder' ),
 ),
 'show_pagination' => array(
 'label' => esc_html__( 'Show Pagination', 'et_builder' ),
 'type' => 'yes_no_button',
 'option_category' => 'configuration',
 'options' => array(
 'on' => esc_html__( 'Yes', 'et_builder' ),
 'off' => esc_html__( 'No', 'et_builder' ),
 ),
 'description' => esc_html__( 'Turn pagination on and off.', 'et_builder' ),
 ),
 'offset_number' => array(
 'label' => esc_html__( 'Offset Number', 'et_builder' ),
 'type' => 'text',
 'option_category' => 'configuration',
 'description' => esc_html__( 'Choose how many posts you would like to offset by', 'et_builder' ),
 ),
 'use_overlay' => array(
 'label' => esc_html__( 'Featured Image Overlay', 'et_builder' ),
 'type' => 'yes_no_button',
 'option_category' => 'layout',
 'options' => array(
 'off' => esc_html__( 'Off', 'et_builder' ),
 'on' => esc_html__( 'On', 'et_builder' ),
 ),
 'affects' => array(
 '#et_pb_overlay_icon_color',
 '#et_pb_hover_overlay_color',
 '#et_pb_hover_icon',
 ),
 'description' => esc_html__( 'If enabled, an overlay color and icon will be displayed when a visitors hovers over the featured image of a post.', 'et_builder' ),
 ),
 'overlay_icon_color' => array(
 'label' => esc_html__( 'Overlay Icon Color', 'et_builder' ),
 'type' => 'color',
 'custom_color' => true,
 'depends_show_if' => 'on',
 'description' => esc_html__( 'Here you can define a custom color for the overlay icon', 'et_builder' ),
 ),
 'hover_overlay_color' => array(
 'label' => esc_html__( 'Hover Overlay Color', 'et_builder' ),
 'type' => 'color-alpha',
 'custom_color' => true,
 'depends_show_if' => 'on',
 'description' => esc_html__( 'Here you can define a custom color for the overlay', 'et_builder' ),
 ),
 'hover_icon' => array(
 'label' => esc_html__( 'Hover Icon Picker', 'et_builder' ),
 'type' => 'text',
 'option_category' => 'configuration',
 'class' => array( 'et-pb-font-icon' ),
 'renderer' => 'et_pb_get_font_icon_list',
 'renderer_with_field' => true,
 'depends_show_if' => 'on',
 'description' => esc_html__( 'Here you can define a custom icon for the overlay', 'et_builder' ),
 ),
 'background_layout' => array(
 'label' => esc_html__( 'Text Color', 'et_builder' ),
 'type' => 'select',
 'option_category' => 'color_option',
 'options' => array(
 'light' => esc_html__( 'Dark', 'et_builder' ),
 'dark' => esc_html__( 'Light', 'et_builder' ),
 ),
 'depends_default' => true,
 'description' => esc_html__( 'Here you can choose whether your text should be light or dark. If you are working with a dark background, then your text should be light. If your background is light, then your text should be set to dark.', 'et_builder' ),
 ),
 'masonry_tile_background_color' => array(
 'label' => esc_html__( 'Grid Tile Background Color', 'et_builder' ),
 'type' => 'color-alpha',
 'custom_color' => true,
 'tab_slug' => 'advanced',
 'depends_show_if' => 'off',
 ),
 'use_dropshadow' => array(
 'label' => esc_html__( 'Use Dropshadow', 'et_builder' ),
 'type' => 'yes_no_button',
 'option_category' => 'layout',
 'options' => array(
 'off' => esc_html__( 'Off', 'et_builder' ),
 'on' => esc_html__( 'On', 'et_builder' ),
 ),
 'tab_slug' => 'advanced',
 'depends_show_if' => 'off',
 ),
 'disabled_on' => array(
 'label' => esc_html__( 'Disable on', 'et_builder' ),
 'type' => 'multiple_checkboxes',
 'options' => array(
 'phone' => esc_html__( 'Phone', 'et_builder' ),
 'tablet' => esc_html__( 'Tablet', 'et_builder' ),
 'desktop' => esc_html__( 'Desktop', 'et_builder' ),
 ),
 'additional_att' => 'disable_on',
 'option_category' => 'configuration',
 'description' => esc_html__( 'This will disable the module on selected devices', 'et_builder' ),
 ),
 'admin_label' => array(
 'label' => esc_html__( 'Admin Label', 'et_builder' ),
 'type' => 'text',
 'description' => esc_html__( 'This will change the label of the module in the builder for easy identification.', 'et_builder' ),
 ),
 'module_id' => array(
 'label' => esc_html__( 'CSS ID', 'et_builder' ),
 'type' => 'text',
 'option_category' => 'configuration',
 'tab_slug' => 'custom_css',
 'option_class' => 'et_pb_custom_css_regular',
 ),
 'module_class' => array(
 'label' => esc_html__( 'CSS Class', 'et_builder' ),
 'type' => 'text',
 'option_category' => 'configuration',
 'tab_slug' => 'custom_css',
 'option_class' => 'et_pb_custom_css_regular',
 ),
 );
 return $fields;
 }

 function shortcode_callback( $atts, $content = null, $function_name ) {
 $module_id = $this->shortcode_atts['module_id'];
 $module_class = $this->shortcode_atts['module_class'];
 $fullwidth = $this->shortcode_atts['fullwidth'];
 $posts_number = $this->shortcode_atts['posts_number'];
 $include_categories = $this->shortcode_atts['include_categories'];
 $meta_date = $this->shortcode_atts['meta_date'];
 $show_thumbnail = $this->shortcode_atts['show_thumbnail'];
 $show_content = $this->shortcode_atts['show_content'];
 $show_author = $this->shortcode_atts['show_author'];
 $show_date = $this->shortcode_atts['show_date'];
 $show_categories = $this->shortcode_atts['show_categories'];
 $show_comments = $this->shortcode_atts['show_comments'];
 $show_pagination = $this->shortcode_atts['show_pagination'];
 $background_layout = $this->shortcode_atts['background_layout'];
 $show_more = $this->shortcode_atts['show_more'];
 $offset_number = $this->shortcode_atts['offset_number'];
 $masonry_tile_background_color = $this->shortcode_atts['masonry_tile_background_color'];
 $use_dropshadow = $this->shortcode_atts['use_dropshadow'];
 $overlay_icon_color = $this->shortcode_atts['overlay_icon_color'];
 $hover_overlay_color = $this->shortcode_atts['hover_overlay_color'];
 $hover_icon = $this->shortcode_atts['hover_icon'];
 $use_overlay = $this->shortcode_atts['use_overlay'];

 global $paged;

 $module_class = ET_Builder_Element::add_module_order_class( $module_class, $function_name );

 $container_is_closed = false;

 // remove all filters from WP audio shortcode to make sure current theme doesn't add any elements into audio module
 remove_all_filters( 'wp_audio_shortcode_library' );
 remove_all_filters( 'wp_audio_shortcode' );
 remove_all_filters( 'wp_audio_shortcode_class');

 if ( '' !== $masonry_tile_background_color ) {
 ET_Builder_Element::set_style( $function_name, array(
 'selector' => '%%order_class%%.et_pb_blog_grid .et_pb_post',
 'declaration' => sprintf(
 'background-color: %1$s;',
 esc_html( $masonry_tile_background_color )
 ),
 ) );
 }

 if ( '' !== $overlay_icon_color ) {
 ET_Builder_Element::set_style( $function_name, array(
 'selector' => '%%order_class%% .et_overlay:before',
 'declaration' => sprintf(
 'color: %1$s !important;',
 esc_html( $overlay_icon_color )
 ),
 ) );
 }

 if ( '' !== $hover_overlay_color ) {
 ET_Builder_Element::set_style( $function_name, array(
 'selector' => '%%order_class%% .et_overlay',
 'declaration' => sprintf(
 'background-color: %1$s;',
 esc_html( $hover_overlay_color )
 ),
 ) );
 }

 if ( 'on' === $use_overlay ) {
 $data_icon = '' !== $hover_icon
 ? sprintf(
 ' data-icon="%1$s"',
 esc_attr( et_pb_process_font_icon( $hover_icon ) )
 )
 : '';

 $overlay_output = sprintf(
 '<span class="et_overlay%1$s"%2$s></span>',
 ( '' !== $hover_icon ? ' et_pb_inline_icon' : '' ),
 $data_icon
 );
 }

 $overlay_class = 'on' === $use_overlay ? ' et_pb_has_overlay' : '';

 if ( 'on' !== $fullwidth ){
 if ( 'on' === $use_dropshadow ) {
 $module_class .= ' et_pb_blog_grid_dropshadow';
 }

 wp_enqueue_script( 'salvattore' );

 $background_layout = 'light';
 }

 $args = array( 'posts_per_page' => (int) $posts_number );

 $et_paged = is_front_page() ? get_query_var( 'page' ) : get_query_var( 'paged' );

 if ( is_front_page() ) {
 $paged = $et_paged;
 }

 if ( '' !== $include_categories )
 $args['cat'] = $include_categories;

 if ( ! is_search() ) {
 $args['paged'] = $et_paged;
 }

 if ( '' !== $offset_number && ! empty( $offset_number ) ) {
 /**
 * Offset + pagination don't play well. Manual offset calculation required
 * @see: https://codex.wordpress.org/Making_Custom_Queries_using_Offset_and_Pagination
 */
 if ( $paged > 1 ) {
 $args['offset'] = ( ( $et_paged - 1 ) * intval( $posts_number ) ) + intval( $offset_number );
 } else {
 $args['offset'] = intval( $offset_number );
 }
 }

 if ( is_single() && ! isset( $args['post__not_in'] ) ) {
 $args['post__not_in'] = array( get_the_ID() );
 }

 ob_start();

 query_posts( $args );

 if ( have_posts() ) {
 while ( have_posts() ) {
 the_post();

 $post_format = et_pb_post_format();

 $thumb = '';

 $width = 'on' === $fullwidth ? 1080 : 400;
 $width = (int) apply_filters( 'et_pb_blog_image_width', $width );

 $height = 'on' === $fullwidth ? 675 : 250;
 $height = (int) apply_filters( 'et_pb_blog_image_height', $height );
 $classtext = 'on' === $fullwidth ? 'et_pb_post_main_image' : '';
 $titletext = get_the_title();
 $thumbnail = get_thumbnail( $width, $height, $classtext, $titletext, $titletext, false, 'Blogimage' );
 $thumb = $thumbnail["thumb"];

 $no_thumb_class = '' === $thumb || 'off' === $show_thumbnail ? ' et_pb_no_thumb' : '';

 if ( in_array( $post_format, array( 'video', 'gallery' ) ) ) {
 $no_thumb_class = '';
 } ?>

 <article id="post-<?php the_ID(); ?>" <?php post_class( 'et_pb_post' . $no_thumb_class . $overlay_class ); ?>>
 
 <h2 class="entry-title blog-2" style="position: relative; top: -20px; margin-bottom: 10px;"><a href="<?php esc_url( the_permalink() ); ?>"><?php the_title(); ?></a></h2>

 <?php
 et_divi_post_format_content();

 if ( ! in_array( $post_format, array( 'link', 'audio', 'quote' ) ) ) {
 if ( 'video' === $post_format && false !== ( $first_video = et_get_first_video() ) ) :
 printf(
 '<div class="et_main_video_container">
 %1$s
 </div>',
 $first_video
 );
 elseif ( 'gallery' === $post_format ) :
 et_pb_gallery_images( 'slider' );
 elseif ( '' !== $thumb && 'on' === $show_thumbnail ) :
 if ( 'on' !== $fullwidth ) echo '<div class="et_pb_image_container">'; ?>
 <a href="<?php esc_url( the_permalink() ); ?>" class="entry-featured-image-url">
 <?php print_thumbnail( $thumb, $thumbnail["use_timthumb"], $titletext, $width, $height ); ?>
 <?php if ( 'on' === $use_overlay ) {
 echo $overlay_output;
 } ?>
 </a>
 <?php
 if ( 'on' !== $fullwidth ) echo '</div> <!-- .et_pb_image_container -->';
 endif;
 } ?>

 <?php if ( 'off' === $fullwidth || ! in_array( $post_format, array( 'link', 'audio', 'quote' ) ) ) { ?>
 <?php if ( ! in_array( $post_format, array( 'link', 'audio' ) ) ) { ?>
 <?php } ?>

 <?php
 if ( 'on' === $show_author || 'on' === $show_date || 'on' === $show_categories || 'on' === $show_comments ) {
 printf( '<p class="post-meta">%1$s %2$s %3$s %4$s %5$s %6$s %7$s</p>',
 (
 'on' === $show_author
 ? sprintf( __( 'by %s', 'et_builder' ), '<span class="author vcard">' . et_pb_get_the_author_posts_link() . '</span>' ) 
 : ''
 ),
 (
 ( 'on' === $show_author && 'on' === $show_date )
 ? ' | '
 : ''
 ),
 (
 'on' === $show_date
 ? sprintf( __( '%s', 'et_builder' ), '<span class="published">' . esc_html( get_the_date( $meta_date ) ) . '</span>' ) 
 : ''
 ),
 (
 (( 'on' === $show_author || 'on' === $show_date ) && 'on' === $show_categories)
 ? ' | '
 : ''
 ),
 (
 'on' === $show_categories
 ? get_the_category_list(', ')
 : ''
 ),
 (
 (( 'on' === $show_author || 'on' === $show_date || 'on' === $show_categories ) && 'on' === $show_comments)
 ? ' | '
 : ''
 ),
 (
 'on' === $show_comments
 ? sprintf( esc_html( _nx( '1 Comment', '%s Comments', get_comments_number(), 'number of comments', 'et_builder' ) ), number_format_i18n( get_comments_number() ) )
 : ''
 )
 );
 }

 $post_content = get_the_content();

 // do not display the content if it contains Blog, Post Slider, Fullwidth Post Slider, or Portfolio modules to avoid infinite loops
 if ( ! has_shortcode( $post_content, 'et_pb_blog' ) && ! has_shortcode( $post_content, 'et_pb_portfolio' ) && ! has_shortcode( $post_content, 'et_pb_post_slider' ) && ! has_shortcode( $post_content, 'et_pb_fullwidth_post_slider' ) ) {
 if ( 'on' === $show_content ) {
 global $more;

 // page builder doesn't support more tag, so display the_content() in case of post made with page builder
 if ( et_pb_is_pagebuilder_used( get_the_ID() ) ) {
 $more = 1;
 the_content();
 } else {
 $more = null;
 the_content( esc_html__( 'read more...', 'et_builder' ) );
 }
 } else {
 if ( has_excerpt() ) {
 the_excerpt();
 } else {
 truncate_post( 270 );
 }
 }
 } else if ( has_excerpt() ) {
 the_excerpt();
 }

 if ( 'on' !== $show_content ) {
 $more = 'on' == $show_more ? sprintf( ' <a href="%1$s" class="more-link" >%2$s</a>' , esc_url( get_permalink() ), esc_html__( 'read more', 'et_builder' ) ) : '';
 echo $more;
 }
 ?>
 <?php } // 'off' === $fullwidth || ! in_array( $post_format, array( 'link', 'audio', 'quote', 'gallery' ?>

 </article> <!-- .et_pb_post -->
 <?php
 } // endwhile

 if ( 'on' === $show_pagination && ! is_search() ) {
 echo '</div> <!-- .et_pb_posts -->';

 $container_is_closed = true;

 if ( function_exists( 'wp_pagenavi' ) ) {
 wp_pagenavi();
 } else {
 if ( et_is_builder_plugin_active() ) {
 include( ET_BUILDER_PLUGIN_DIR . 'includes/navigation.php' );
 } else {
 get_template_part( 'includes/navigation', 'index' );
 }
 }
 }

 wp_reset_query();
 } else {
 if ( et_is_builder_plugin_active() ) {
 include( ET_BUILDER_PLUGIN_DIR . 'includes/no-results.php' );
 } else {
 get_template_part( 'includes/no-results', 'index' );
 }
 }

 $posts = ob_get_contents();

 ob_end_clean();

 $class = " et_pb_module et_pb_bg_layout_{$background_layout}";

 $output = sprintf(
 '<div%5$s class="%1$s%3$s%6$s"%7$s>
 %2$s
 %4$s',
 ( 'on' === $fullwidth ? 'et_pb_posts' : 'et_pb_blog_grid clearfix' ),
 $posts,
 esc_attr( $class ),
 ( ! $container_is_closed ? '</div> <!-- .et_pb_posts -->' : '' ),
 ( '' !== $module_id ? sprintf( ' id="%1$s"', esc_attr( $module_id ) ) : '' ),
 ( '' !== $module_class ? sprintf( ' %1$s', esc_attr( $module_class ) ) : '' ),
 ( 'on' !== $fullwidth ? ' data-columns' : '' )
 );

 if ( 'on' !== $fullwidth )
 $output = sprintf( '<div class="et_pb_blog_grid_wrapper">%1$s</div>', $output );

 return $output;
 }
}
new DS_Custom_Module_Blog;

Stephen James

SJ is a web developer living in the coastal town of Southsea, England. He is a Divi and WordPress advocate and the founder of Divi Space.

Previous post
Next post

50 Comments

  1. Rebekah

    Hi, SJ. So, this isn’t adding a brand new module but commandeering one that already exists? The old blog module has been replaced with the new one? There aren’t two options for the module, correct?

    Thank you,
    rbkh

    Reply
    • Stephen

      Hey 🙂 No, this would create a new module it’s just easier to use the existing setup of a module for reference, but not crucial.

    • Stephen

      Hey 🙂 No, this would create a new module it’s just easier to use the existing setup of a module for reference, but not crucial.

  2. Adam

    Great stuff, like always! I’ll give it a try my next Divi customization.
    Thanks, SJ 🙂

    Reply
  3. Dave

    This was great SJ!
    I have a question, how did you work out what is needed for the functions.php file.
    If I wanted to make another blurb type module,where would I look or how would I determine the correct code for the functions.php part of this tutorial?
    Thank you.
    D.

    Reply
  4. Horia

    Hello SJ, thanks for all the great Divi tutorials and documentation you offer. I am a junior Web Dev taking my first steps through WordPress and I only recently discovered the Divi theme. Please tell me, is there any way I can use the Divi Builder to generate content and then populate it with WordPress content (f.e. Custom Post Types) through a WP_Query ? I have searched the internet for 2 days now and I think your article is the closest I got to what I want. Looking forward in your reply! Cheers!

    Reply
  5. Horia

    Ok. So I have created the Blog – Custom Grid module and it appears in the builder as it should. Can you tell me if I can tie my custom module to bring in content from a Custom Post Type in order to generate dynamic content ? It would be awesome.

    Reply
    • Ben

      Hey Horia,

      I like the sound of what you mentioned and I am working on something similar. If you want to drop me an email then we could discuss what you had in mind.

      Ben
      ben[@]noou[.]co[.]uk

    • Ollie

      Hi Horia and Ben, your comments are exactly what I am trying to achieve. I would love to know how you’re going with this.

      You can email me at ocoady[@]gmail[.]com

    • Chris

      Have any of you developed a solution for this that you could point me to also? I am also attempting the same and would greatly value if you can move me further down the field faster.

      If you don’t mind, could you email me at chris[@]hisandheranderson[.]com

  6. Martin conde

    Great tutorial, thanks a lot!

    I was wondering.. Would be possible to somehow copy the code of some of the extra theme blog modules and inject it into divi using the steps of your tutorial? That’d be gold:)

    Reply
    • Stephen

      In theory, there’s no reason you couldn’t do this 🙂 I’m sure it would be a great option for many.

    • Adam

      That’s exactly what I was trying to do, since I am dissapointed how Extra Theme restricts usage of the Divi builder… But that is a little more complicated. I didn’t find where the Extra modules resides. In the …>includes>builder>main-modules.php there are only the native Divi modules. Can anyone be so kind and tell me where to look for the Extra modules? Thanks 🙂

  7. Mhluzi

    Hi Stephen

    This looks like exactly what I need – I have copied and pasted your code but the new module is not showing up.

    I have checked and double checked my code and the only thing I can think of at this stage is a possible version issue.

    I am using Divi 2.7.3 with a simple Divi Child theme.. do you think that could be an issue?

    Any ideas would be greatly appreciated!

    Cheers
    M

    Reply
  8. Mhluzi

    Hi Stephen

    I reached out for help too soon!

    Seems to be working now so all good!

    Cheers

    M

    Reply
  9. Stephen

    Stephen, I’m so happy I found your website. I’m currently customizing a header nav to function as a sticky menu at the foot of the site. I’ve been eyeing all your articles and I’m ecstatic. I can’t believe I never thought of customizing divi modules like you have in this tutorial. You make it look so easy. Thank you so much 🙂

    Reply
    • Mhluzi

      Hi Stephen

      I had got this up and running and have noticed one thing. When inserting a new module it appears fine but in the Blog Module Settings, General Settings tab it doesn’t have the option “Layout” to choose between Fullwidth and Grid.

      I have changed the line in the DS_Custom_Module_Blog class to read:
      ‘fullwidth’ => array( ‘on’ ),

      But that didn’t seem to help.

      But, if, in the DS_Custom_Module_Blog class, I change the line :
      $this->slug = ‘et_pb_blog_2’;
      to
      $this->slug = ‘et_pb_blog’;

      then the “Layout” option appears but the original Blog module then disappears.

      Have you experienced that and if so do you perhaps know a way around it?

      Cheers

      M.

  10. Dawson

    For all the novice php folks out there: I just spent an hour not sure why it wouldn’t work, finally realized that of course you have to change the class in the last line of the function as well (new [class]), so for anyone having trouble don’t forget to do that!

    This is hugely useful, thanks for the great post!

    Reply
  11. David

    HI Mhluzi,

    I don’t know if you found your answer to your question yet or not.

    You need to use a name other than “blog”. The code is setup to not give access to certain features if you use the words, “blog, Post Slider, Fullwidth Post Slider, or Portfolio” to avoid creating an infinite loop.

    Just use something else in its place and everything will work correctly.

    Hope that helps.

    Regards,
    David

    Reply
  12. Terry

    SJ, thanks a ton for providing this. It opens up a whole new world of opportunities.

    One thing I’ve found is that the slug name *must* begin with “et_pb_”. Otherwise the shortcode callbacks don’t work, and the module will not be saved to the builder.

    For example, I used “n10s_pb_image” as the slug. The module would appear in the builder with all the custom options, but it would not save.

    Took several hours over three days to figure that out lol. 🙂

    Reply
    • Denis

      Thank you Terry! You saved my day!:)

  13. Thomas

    I explored the Divi theme to create a new module by duplicating an existing one. But got an issue. I even could insert the new module and it was displayed on the page but the generated code has not the closing shortcode.

    I checked the database table to get the code: Normal module is coded like: [module_slug]content[/module_slug]
    But the code of my new module I created is: [my_new_module_slug]content
    {without the closing shortcode tag}
    This caused, that in the backend, when editing the page, my new module isn’t displayed, even the code contains still the shortcode of my new module.
    So I was happy to discover your article here. But after applying your steps I’m wondering now, why I still have the same problem, inspite I followed exactly the steps you are explaining here – OK the most are identical with what I did, but there are quite some differences.

    No one here seems to have the issue I have – so I don’t understand it. May it depend on the newest version 2.7.5 of DIVI, I’m using?

    Reply
    • Stephen

      Hi Thomas, it could be.. I haven’t tested this with the latest version. i’ll give it a try and let you know what I come up with 🙂

    • Thomas

      Now it seems to work. But I don’t know why. In the first module I created, I changed more text items starting with ‘et_pb_’ to something like ‘t3d_’ as my own identifier. In the second one I left the most of all as it was in the original Divi module. Now the issue has gone. I can insert and save the new module and it is still diplayed even in the backend of an edited page.

      I want to build a filterable service module for a seperately defined custom post type ‘service’ with a divi type taxonomy ‘service-type’. Now I discover several dependencies and a lot of things that has to be changed and added in comparison to the original module I used as base, the filterable portfolio module which uses the project post type of Divi. Now it seems to me to be possible to do that, but it’s probably much more work than I had expected. Anyway exiting job!

  14. Bill

    Using latest wp and Divi 2.7 with a child theme. So far so good. I’m going to try customizing the blurb module wrapping it in a div so the whole div is clickable and on hover animate the elements inside, title, icon etc.
    Thanks for this code Stephen. Do you think it will need to change with 3.0?

    Reply
  15. Reak

    Greate tutorial, how put author name above the featured image to?
    what You changed to put titele above image?

    Reply
  16. Kim Joy Fox

    Your instructions were great. I got it all working, with the new module showing up. But when I tried to add another text field, I can’t get the extra field to show up. I tried several different names for the field, added it to the “$this->whitelisted_fields” array, the “$fields” array, the function “shortcode_callback”, and the output, but the field won’t show up in the module in the backend of the site. Do you know if it’s possible to add more fields to a module?

    Reply
  17. fxbodin

    Hi!
    First, thanks for sharing the method.

    I (believe I) have followed the steps, and near everything works well, except that, in the WP page editor after I submit the page including he custom module, when refreshing after submit, the DIVI editor doesn’t show the module no more. BUT, the website displays the custom module content.
    Have you any idea on what I did wrong?
    DIVI Version: 2.7.5
    Wordpress Version 4.5.3
    Thanks in advance and best regards.

    fxbodin

    Reply
  18. Theresa

    Hi is this the same way to add a custom post type to look like the blog grid or do you have to create a page template. I want to build my own custom post types not use a third party plugin if possible but it is really hard to get it to use as a module.

    Reply
  19. Theresa

    Hi I tried to change this for a custom post type and it works till a point. I modelled off this one and the filtered portfolio module but it won’t get the categories or taxonomy terms from my custom post type only the blog or portfolio. I changed the names in the new module but it isn’t working. Do you have any idea why it isn’t grabbing it?

    Reply
  20. Andrea

    Hi Stephen,
    Do you reckon this is something which we’ll be compatible with Divi 3.0?

    Reply
    • Andrea

      will* ..sorry

  21. Alex

    Thanks!

    Reply
  22. zalman

    Any idea whether this would work with divi 3.0?

    Reply
  23. Rajeshkannan MJ

    Can you please let me know why it is not working for Divi 3 ? I think the admin check you are doing the function makes not to work in frontend builder?

    Reply
  24. Marco

    Someone tried in Extra theme? What changes?

    Reply
  25. Matt Adams

    I’ve had a few challenges with some custom modules and 3.0.1 and their appearance on the new front end builder. I’d be really curious to hear if you’ve had an success adapting custom modules to appear on the front end builder interface.

    Reply
  26. Gabriel

    Not working correctly on Divi 3 Visual Builder. How adding module in Divi 3 with support Visual Builder?

    Reply
    • Jorge L Saud

      add_action(‘et_builder_framework_loaded’,’DiviLoadGallery’);
      function DiviLoadGallery(){
      include(“divi-slick-gallery.php”);
      }
      class ET_Divi_Slick_Gallery extends ET_Builder_Module{

      }

    • Rui

      Hi were you able to add the modules to Visual Builder?
      Thanks!

  27. Flo Nelson

    Awesome! I need to do this and so happy to have this guide already written up!

    Reply
  28. Sergio

    Just in case someone is wondering, the snippet does not work with the latest version of Extra (I was getting an infinite loop). However, after sever hours of trying, I decided to copy the content of includes/builder/module/Blog.php from the parent theme, following the instructions in this article. Everything worked great :).

    Reply
  29. Hristo

    It works in Divi 3 but for some reason when using it it inserts new containers and if I set ID to the module it sets it two times. I made some modifications to the portfolio plugin and here is what I get:

    Does anybody has any idea why is this happening?

    Reply
    • Hristo

      I forgot to mention that it does this even if not modifying the code at all, just registering the new plugin. Thank you!

  30. Seth

    I copied this exactly, except I swapped the blog module for the accordion module. The new module is not appearing in builder. Any ideas? Thanks!

    Reply
  31. Travis

    I’m working on trying to create a custom project slider based on the project slider. Using the guidance from this post, I’m seeing my new slider module in the list of modules. However, when I click on it to apply the settings, it seems to be pulling the default slider options. The label changes in my custom file are showing. Thoughts?

    Reply
  32. Jack

    This tutorial is great! Now I cane some extra styles to Divi modules!

    Reply
  33. Aldin

    Super awesome! Thanks for updating this tutorial. 🙂

    Reply
  34. Sarah Crawford

    Hi All! I’ve done the above and get an error – failed with code 1, could not install as does not contain package.json

    Any ideas?

    Reply

Submit a Comment

Your email address will not be published. Required fields are marked *

Receive notifications about our new blog posts.