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 🙂
- A Local Environment (like Flywheel Local, Desktop Server or Docker).
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. 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: 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. 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
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: 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;
Hi
I’m creating a child theme and i will create new module. How can i include my new module into the child theme? Must i create an extension?
Thanks.
Elodie
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?
Super awesome! Thanks for updating this tutorial. 🙂
This tutorial is great! Now I cane some extra styles to Divi modules!
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?
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!
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?
For some reason it does not render the HTML code, so here it is: https://pastebin.com/ymRDezpT
I forgot to mention that it does this even if not modifying the code at all, just registering the new plugin. Thank you!
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 :).
Awesome! I need to do this and so happy to have this guide already written up!
Not working correctly on Divi 3 Visual Builder. How adding module in Divi 3 with support Visual Builder?
add_action(‘et_builder_framework_loaded’,’DiviLoadGallery’);
function DiviLoadGallery(){
include(“divi-slick-gallery.php”);
}
class ET_Divi_Slick_Gallery extends ET_Builder_Module{
…
}
Hi were you able to add the modules to Visual Builder?
Thanks!
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.
Someone tried in Extra theme? What changes?
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?
Any idea whether this would work with divi 3.0?
Thanks!
Hi Stephen,
Do you reckon this is something which we’ll be compatible with Divi 3.0?
will* ..sorry
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?
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.
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
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?
Greate tutorial, how put author name above the featured image to?
what You changed to put titele above image?
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?
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?
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 🙂
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!
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. 🙂
Thank you Terry! You saved my day!:)
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
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!
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 🙂
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.
Hi Stephen
I reached out for help too soon!
Seems to be working now so all good!
Cheers
M
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
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:)
In theory, there’s no reason you couldn’t do this 🙂 I’m sure it would be a great option for many.
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 🙂
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.
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
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
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
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!
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.
Great stuff, like always! I’ll give it a try my next Divi customization.
Thanks, SJ 🙂
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
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.
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.