Build Custom Elementor Widgets

Elementor is a powerful tool for creating eye-catching & engaging websites. The default toolset of widgets it offers is extensive, but what if you want to build your own? In this article I'll go over how to build your own custom Elementor widgets.
Published
Updated
Typical Read
20 minutes
Sponsored
Sponsor my site by donating to help support open-source projects, like WordPress Zero Spam, Referrer Analytics & others.
Find your next web developer job
jobs by Indeed

Build Custom Elementor Widgets was originally published on Mar 4, 2019 and has recently been updated on Aug 5, 2020 to reflect emerging trends.

Elementor is packed with various widgets. They help build eye-catching sites, but sometimes we need specific functionality. To do this, you’ll need to build a custom Elementor widget — here’s how.

How to Build Custom Elementor Widgets

In this article, we’ll build a simple custom Elementor widget that allows the user to edit text, images and styles both in the widget settings and inline! We’ll call this widget, Elementor Awesomesauce.

Step 1. Create a plugin for your Elementor widgets.

It’s best practice to build an Elementor widgets plugin that’ll hold your Elementor widgets vs. building them directly in the theme. Though possible, it’s not recommended since it strays from the stand-alone component approach. This way your widget’s can be used on any theme vs. being theme-specific.

To begin let’s start with creating a custom plugin, we’ll call it Elementor Awesomesauce plugin:

  1. Create a directory called elementor-awesomesauce in wp-content/plugins
  2. In the new directory, create the following files & directories:
    • elementor-awesomesauce.php – initializes the plugin & verifies requirements
    • plugin.php – registers the plugin’s custom widgets & scripts
    • widgets/awesomesauce.php – the custom Awesomesauce Elementor widget
    • assets/js/awesomesauce.js – some JS for the custom widget
    • assets/css – directory for the plugin CSS files

Step 2. Create the plugin initialization file.

In the elementor-awesomesauce.php file, we’ll initialize the plugin and verify the core Elementor plugin is installed, activate and the minimum PHP version is met.

elementor-awesomesauce.php

<?php
/**
 * Plugin Name: Elementor Awesomesauce
 * Description: A simple Elementor Awesomesauce plugin that holds some most excellent custom widgets.
 * Plugin URI:  https://benmarshall.me/build-custom-elementor-widgets/
 * Version:     1.0.0
 * Author:      Ben Marshall
 * Author URI:  https://benmarshall.me
 * Text Domain: elementor-awesomesauce
 */

if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly

/**
 * Main Elementor Awesomesauce Class
 *
 * The init class that runs the Elementor Awesomesauce plugin.
 * Intended To make sure that the plugin's minimum requirements are met.
 *
 * You should only modify the constants to match your plugin's needs.
 *
 * Any custom code should go inside Plugin Class in the plugin.php file.
 * @since 1.0.0
 */
final class Elementor_Awesomesauce {

	/**
	 * Plugin Version
	 *
	 * @since 1.0.0
	 * @var string The plugin version.
	 */
	const VERSION = '1.0.0';

	/**
	 * Minimum Elementor Version
	 *
	 * @since 1.0.0
	 * @var string Minimum Elementor version required to run the plugin.
	 */
	const MINIMUM_ELEMENTOR_VERSION = '2.0.0';

	/**
	 * Minimum PHP Version
	 *
	 * @since 1.0.0
	 * @var string Minimum PHP version required to run the plugin.
	 */
	const MINIMUM_PHP_VERSION = '7.0';

	/**
	 * Constructor
	 *
	 * @since 1.0.0
	 * @access public
	 */
	public function __construct() {
    // Load translation
		add_action( 'init', array( $this, 'i18n' ) );

		// Init Plugin
		add_action( 'plugins_loaded', array( $this, 'init' ) );
	}

	/**
	 * Load Textdomain
	 *
	 * Load plugin localization files.
	 * Fired by `init` action hook.
	 *
	 * @since 1.2.0
	 * @access public
	 */
	public function i18n() {
    load_plugin_textdomain( 'elementor-awesomesauce' );
	}

	/**
	 * Initialize the plugin
	 *
	 * Validates that Elementor is already loaded.
	 * Checks for basic plugin requirements, if one check fail don't continue,
	 * if all check have passed include the plugin class.
	 *
	 * Fired by `plugins_loaded` action hook.
	 *
	 * @since 1.2.0
	 * @access public
	 */
	public function init() {

		// Check if Elementor installed and activated
		if ( ! did_action( 'elementor/loaded' ) ) {
			add_action( 'admin_notices', array( $this, 'admin_notice_missing_main_plugin' ) );
			return;
		}

		// Check for required Elementor version
		if ( ! version_compare( ELEMENTOR_VERSION, self::MINIMUM_ELEMENTOR_VERSION, '>=' ) ) {
			add_action( 'admin_notices', array( $this, 'admin_notice_minimum_elementor_version' ) );
			return;
		}

		// Check for required PHP version
		if ( version_compare( PHP_VERSION, self::MINIMUM_PHP_VERSION, '<' ) ) {
			add_action( 'admin_notices', array( $this, 'admin_notice_minimum_php_version' ) );
			return;
		}

		// Once we get here, We have passed all validation checks so we can safely include our plugin
		require_once( 'plugin.php' );
	}

	/**
	 * Admin notice
	 *
	 * Warning when the site doesn't have Elementor installed or activated.
	 *
	 * @since 1.0.0
	 * @access public
	 */
	public function admin_notice_missing_main_plugin() {
		if ( isset( $_GET['activate'] ) ) {
			unset( $_GET['activate'] );
		}

		$message = sprintf(
			/* translators: 1: Plugin name 2: Elementor */
			esc_html__( '"%1$s" requires "%2$s" to be installed and activated.', 'elementor-awesomesauce' ),
			'<strong>' . esc_html__( 'Elementor Awesomesauce', 'elementor-awesomesauce' ) . '</strong>',
			'<strong>' . esc_html__( 'Elementor', 'elementor-awesomesauce' ) . '</strong>'
		);

		printf( '<div class="notice notice-warning is-dismissible"><p>%1$s</p></div>', $message );
	}

	/**
	 * Admin notice
	 *
	 * Warning when the site doesn't have a minimum required Elementor version.
	 *
	 * @since 1.0.0
	 * @access public
	 */
	public function admin_notice_minimum_elementor_version() {
		if ( isset( $_GET['activate'] ) ) {
			unset( $_GET['activate'] );
		}

		$message = sprintf(
			/* translators: 1: Plugin name 2: Elementor 3: Required Elementor version */
			esc_html__( '"%1$s" requires "%2$s" version %3$s or greater.', 'elementor-awesomesauce' ),
			'<strong>' . esc_html__( 'Elementor Awesomesauce', 'elementor-awesomesauce' ) . '</strong>',
			'<strong>' . esc_html__( 'Elementor', 'elementor-awesomesauce' ) . '</strong>',
			self::MINIMUM_ELEMENTOR_VERSION
		);

		printf( '<div class="notice notice-warning is-dismissible"><p>%1$s</p></div>', $message );
	}

	/**
	 * Admin notice
	 *
	 * Warning when the site doesn't have a minimum required PHP version.
	 *
	 * @since 1.0.0
	 * @access public
	 */
	public function admin_notice_minimum_php_version() {
		if ( isset( $_GET['activate'] ) ) {
			unset( $_GET['activate'] );
		}

		$message = sprintf(
			/* translators: 1: Plugin name 2: PHP 3: Required PHP version */
			esc_html__( '"%1$s" requires "%2$s" version %3$s or greater.', 'elementor-awesomesauce' ),
			'<strong>' . esc_html__( 'Elementor Awesomesauce', 'elementor-awesomesauce' ) . '</strong>',
			'<strong>' . esc_html__( 'PHP', 'elementor-awesomesauce' ) . '</strong>',
			self::MINIMUM_PHP_VERSION
		);

		printf( '<div class="notice notice-warning is-dismissible"><p>%1$s</p></div>', $message );
	}
}

// Instantiate Elementor_Awesomesauce.
new Elementor_Awesomesauce();

Step 3. Register the widgets & scripts.

In the plugin.php file, we’ll register all of the plugin’s widgets & scripts (JS & CSS).

plugin.php

<?php
namespace ElementorAwesomesauce;

/**
 * Class Plugin
 *
 * Main Plugin class
 * @since 1.0.0
 */
class Plugin {

	/**
	 * Instance
	 *
	 * @since 1.0.0
	 * @access private
	 * @static
	 *
	 * @var Plugin The single instance of the class.
	 */
	private static $_instance = null;

	/**
	 * Instance
	 *
	 * Ensures only one instance of the class is loaded or can be loaded.
	 *
	 * @since 1.2.0
	 * @access public
	 *
	 * @return Plugin An instance of the class.
	 */
	public static function instance() {
    if ( is_null( self::$_instance ) ) {
      self::$_instance = new self();
	  }

    return self::$_instance;
	}

	/**
	 * widget_scripts
	 *
	 * Load required plugin core files.
	 *
	 * @since 1.2.0
	 * @access public
	 */
	public function widget_scripts() {
		wp_register_script( 'elementor-awesomesauce', plugins_url( '/assets/js/awesomesauce.js', __FILE__ ), [ 'jquery' ], false, true );
	}

	/**
	 * Include Widgets files
	 *
	 * Load widgets files
	 *
	 * @since 1.2.0
	 * @access private
	 */
	private function include_widgets_files() {
		require_once( __DIR__ . '/widgets/awesomesauce.php' );
	}

	/**
	 * Register Widgets
	 *
	 * Register new Elementor widgets.
	 *
	 * @since 1.2.0
	 * @access public
	 */
	public function register_widgets() {
		// Its is now safe to include Widgets files
		$this->include_widgets_files();

		// Register Widgets
		\Elementor\Plugin::instance()->widgets_manager->register_widget_type( new Widgets\Awesomesauce() );
	}

	/**
	 *  Plugin class constructor
	 *
	 * Register plugin action hooks and filters
	 *
	 * @since 1.2.0
	 * @access public
	 */
	public function __construct() {

		// Register widget scripts
		add_action( 'elementor/frontend/after_register_scripts', [ $this, 'widget_scripts' ] );

		// Register widgets
		add_action( 'elementor/widgets/widgets_registered', [ $this, 'register_widgets' ] );
	}
}

// Instantiate Plugin Class
Plugin::instance();

Step 4. Create the custom widget.

In the widgets/awesomesauce.php file, we’ll build the custom Elementor widget.

widgets/awesomesauce.php

<?php
namespace ElementorAwesomesauce\Widgets;

use Elementor\Widget_Base;
use Elementor\Controls_Manager;

if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly

/**
 * @since 1.1.0
 */
class Awesomesauce extends Widget_Base {

	/**
	 * Retrieve the widget name.
	 *
	 * @since 1.1.0
	 *
	 * @access public
	 *
	 * @return string Widget name.
	 */
	public function get_name() {
		return 'awesomesauce';
	}

	/**
	 * Retrieve the widget title.
	 *
	 * @since 1.1.0
	 *
	 * @access public
	 *
	 * @return string Widget title.
	 */
	public function get_title() {
		return __( 'Awesomesauce', 'elementor-awesomesauce' );
	}

	/**
	 * Retrieve the widget icon.
	 *
	 * @since 1.1.0
	 *
	 * @access public
	 *
	 * @return string Widget icon.
	 */
	public function get_icon() {
		return 'fa fa-pencil';
	}

	/**
	 * Retrieve the list of categories the widget belongs to.
	 *
	 * Used to determine where to display the widget in the editor.
	 *
	 * Note that currently Elementor supports only one category.
	 * When multiple categories passed, Elementor uses the first one.
	 *
	 * @since 1.1.0
	 *
	 * @access public
	 *
	 * @return array Widget categories.
	 */
	public function get_categories() {
		return [ 'general' ];
	}

	/**
	 * Register the widget controls.
	 *
	 * Adds different input fields to allow the user to change and customize the widget settings.
	 *
	 * @since 1.1.0
	 *
	 * @access protected
	 */
	protected function _register_controls() {
		$this->start_controls_section(
			'section_content',
			[
				'label' => __( 'Content', 'elementor-awesomesauce' ),
			]
		);

		$this->add_control(
			'title',
			[
				'label' => __( 'Title', 'elementor-awesomesauce' ),
				'type' => Controls_Manager::TEXT,
				'default' => __( 'Title', 'elementor-awesomesauce' ),
			]
		);

		$this->add_control(
			'description',
			[
				'label' => __( 'Description', 'elementor-awesomesauce' ),
				'type' => Controls_Manager::TEXTAREA,
				'default' => __( 'Description', 'elementor-awesomesauce' ),
			]
		);

		$this->add_control(
			'content',
			[
				'label' => __( 'Content', 'elementor-awesomesauce' ),
				'type' => Controls_Manager::WYSIWYG,
				'default' => __( 'Content', 'elementor-awesomesauce' ),
			]
		);

		$this->end_controls_section();
	}

	/**
	 * Render the widget output on the frontend.
	 *
	 * Written in PHP and used to generate the final HTML.
	 *
	 * @since 1.1.0
	 *
	 * @access protected
	 */
	protected function render() {
		$settings = $this->get_settings_for_display();

		$this->add_inline_editing_attributes( 'title', 'none' );
		$this->add_inline_editing_attributes( 'description', 'basic' );
		$this->add_inline_editing_attributes( 'content', 'advanced' );
		?>
		<h2 <?php echo $this->get_render_attribute_string( 'title' ); ?><?php echo $settings['title']; ?></h2>
		<div <?php echo $this->get_render_attribute_string( 'description' ); ?><?php echo $settings['description']; ?></div>
		<div <?php echo $this->get_render_attribute_string( 'content' ); ?><?php echo $settings['content']; ?></div>
		<?php
	}

	/**
	 * Render the widget output in the editor.
	 *
	 * Written as a Backbone JavaScript template and used to generate the live preview.
	 *
	 * @since 1.1.0
	 *
	 * @access protected
	 */
	protected function _content_template() {
		?>
		<#
		view.addInlineEditingAttributes( 'title', 'none' );
		view.addInlineEditingAttributes( 'description', 'basic' );
		view.addInlineEditingAttributes( 'content', 'advanced' );
		#>
		<h2 {{{ view.getRenderAttributeString( 'title' ) }}}>{{{ settings.title }}}</h2>
		<div {{{ view.getRenderAttributeString( 'description' ) }}}>{{{ settings.description }}}</div>
		<div {{{ view.getRenderAttributeString( 'content' ) }}}>{{{ settings.content }}}</div>
		<?php
	}
}

Next we’ll add some JS to our widget that’ll only be loaded if the widget appears on the page.

assets/js/awesomesauce.js

( function( $ ) {
	/**
 	 * @param $scope The Widget wrapper element as a jQuery element
	 * @param $ The jQuery alias
	 */
	var WidgetAwesomesauceHandler = function( $scope, $ ) {
		console.log( $scope );
	};

	// Make sure you run this code under Elementor.
	$( window ).on( 'elementor/frontend/init', function() {
		elementorFrontend.hooks.addAction( 'frontend/element_ready/awesomesauce.default', WidgetAwesomesauceHandler );
	} );
} )( jQuery );

Step 5. Add configurable fields to the widget.

Elementor is super powerful allowing you to easily add custom tabs, fields, style setting and even responsiveness. In the code above, the _register_controls is where we’ve already added a few fields for title, description and content, but what if you need more? Check out the examples below to add even more awesomeness to your Elementor Awesomesauce plugin.

Add Elementor Widget Tabs

Elementor widgets can have configurable tabbed sections. For instance, many core widgets in Elementor include Content, Style & Advanced tabs. The tabbed sections allow you to add customizable fields and other content specific to each widget instance on a page.

For our Elementor Awesomesauce plugin, we’ll add a Content and Style tab.

Creating a Widget Content Tab

widgets/awesomesauce.php – In the _register_controls method.

$this->start_controls_section(
  'section_content',
  [
    'label' => __( 'Content', 'elementor-awesomesauce' ),
  ]
);

/* Add the options you'd like to show in this tab here */

$this->end_controls_section();
Creating a Widget Style Tab

widgets/awesomesauce.php – In the _register_controls method.

$this->start_controls_section(
  'style_section',
  [
    'label' => __( 'Style Section', 'elementor-awesomesauce' ),
    'tab' => \Elementor\Controls_Manager::TAB_STYLE,
  ]
);

/* Add the options you'd like to show in this tab here */

$this->end_controls_section();

Elementor Widget Image Field

The ability to add/change images is crucial to any good WordPress theme, here’s how to add a custom image field in an Elementor widget with Controls_Manager::MEDIA:

widgets/awesomesauce.php – In the _register_controls method.

$this->add_control(
			'mask_image',
			[
				'label' => __( 'Mask Image', 'elementor-awesomesauce' ),
				'type' => Controls_Manager::MEDIA,
        'default' => [
					'url' => Utils::get_placeholder_image_src(),
				]
			]
		);

What if we wanted to allow the user to select the HTML element for the title, it’s also a cinch with Controls_Manager::SELECT:

widgets/awesomesauce.php – At the top of the file where the use statements are located.

use Elementor\Utils;

widgets/awesomesauce.php – In the _register_controls method.

$this->add_control(
  'mask_image',
  [
    'label' => __( 'Mask Image', 'elementor-awesomesauce' ),
    'type' => Controls_Manager::MEDIA,
    'default' => [
      'url' => Utils::get_placeholder_image_src(),
    ]
  ]
);

Elementor Widget Button Group

How about adding alignment options using a button group, easy with Controls_Manager::CHOOSE:

widgets/awesomesauce.php – In the _register_controls method.

$this->add_control(
  'text_align',
  [
    'label' => __( 'Alignment', 'elementor-awesomesauce' ),
    'type' => \Elementor\Controls_Manager::CHOOSE,
    'options' => [
      'left' => [
        'title' => __( 'Left', 'elementor-awesomesauce' ),
        'icon' => 'fa fa-align-left',
      ],
      'center' => [
        'title' => __( 'Center', 'elementor-awesomesauce' ),
        'icon' => 'fa fa-align-center',
      ],
      'right' => [
        'title' => __( 'Right', 'elementor-awesomesauce' ),
        'icon' => 'fa fa-align-right',
      ],
    ],
    'default' => 'center',
    'toggle' => true,
  ]
);

Elementor Widget Typography Options

Elementor comes with a powerful typography editor that includes responsive options, here’s how you add it with Group_Control_Typography & Scheme_Typography::TYPOGRAPHY_1.

widgets/awesomesauce.php – At the top of the file where the use statements are located.

use Elementor\Group_Control_Typography;
use Elementor\Scheme_Typography;

widgets/awesomesauce.php – In the _register_controls method.

$this->add_group_control(
  Group_Control_Typography::get_type(),
  [
    'name'     => 'content_typography',
    'label'    => __( 'Typography', 'elementor-awesomesauce' ),
    'scheme'   => Scheme_Typography::TYPOGRAPHY_1,
    'selector' => '{{WRAPPER}} .elementor-awesomesauce',
    'fields_options' => [
      'letter_spacing' => [
        'range' => [
          'min' => 0,
          'max' => 100
        ]
      ]
    ]
  ]
);

Adding a Elementor Repeater Control

Elementor repeater control allows you to build repeatable blocks of fields.

You can create, for example, a set of fields that will contain a title and a WYSIWYG text – the user will then be able to add “rows”, and each row will contain a title and a text. The data can be wrapper in custom HTML tags, designed using CSS, and interact using JS or external libraries.

The control is defined in Control_Repeater class which extends Base_Data_Control class.

Note that when using the control, the type should be set using the \Elementor\Controls_Manager::REPEATER constant.

Example Repeater Control Usage

Add the following code in the widgets/awesomesauce.php file where all Elementor widget controls are defined:

<?php
namespace ElementorAwesomesauce\Widgets;
 
use Elementor\Widget_Base;
use Elementor\Controls_Manager;
 
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly

class Elementor_Widget_Test extends \Elementor\Widget_Base {

	public function get_name() {
		return 'awesomesauce';
	}

	public function get_title() {
		return __( 'Awesomesauce', 'elementor-awesomesauce' );
  }
  
  public function get_icon() {
    return 'fa fa-pencil';
  }

  public function get_categories() {
    return [ 'general' ];
  }

	protected function _register_controls() {

		$this->start_controls_section(
			'content_section',
			[
				'label' => __( 'Content', 'elementor-awesomesauce' ),
				'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
			]
		);

		$repeater = new \Elementor\Repeater();

		$repeater->add_control(
			'list_title', [
				'label' => __( 'Title', 'elementor-awesomesauce' ),
				'type' => \Elementor\Controls_Manager::TEXT,
				'default' => __( 'List Title' , 'elementor-awesomesauce' ),
				'label_block' => true,
			]
		);

		$repeater->add_control(
			'list_content', [
				'label' => __( 'Content', 'elementor-awesomesauce' ),
				'type' => \Elementor\Controls_Manager::WYSIWYG,
				'default' => __( 'List Content' , 'elementor-awesomesauce' ),
				'show_label' => false,
			]
		);

		$repeater->add_control(
			'list_color',
			[
				'label' => __( 'Color', 'elementor-awesomesauce' ),
				'type' => \Elementor\Controls_Manager::COLOR,
				'selectors' => [
					'{{WRAPPER}} {{CURRENT_ITEM}}' => 'color: {{VALUE}}'
				],
			]
		);

		$this->add_control(
			'list',
			[
				'label' => __( 'Repeater List', 'elementor-awesomesauce' ),
				'type' => \Elementor\Controls_Manager::REPEATER,
				'fields' => $repeater->get_controls(),
				'default' => [
					[
						'list_title' => __( 'Title #1', 'elementor-awesomesauce' ),
						'list_content' => __( 'Item content. Click the edit button to change this text.', 'elementor-awesomesauce' ),
					],
					[
						'list_title' => __( 'Title #2', 'elementor-awesomesauce' ),
						'list_content' => __( 'Item content. Click the edit button to change this text.', 'elementor-awesomesauce' ),
					],
				],
				'title_field' => '{{{ list_title }}}',
			]
		);

		$this->end_controls_section();

	}

	protected function render() {
		$settings = $this->get_settings_for_display();

		if ( $settings['list'] ) {
			echo '<dl>';
			foreach (  $settings['list'] as $item ) {
				echo '<dt class="elementor-repeater-item-' . $item['_id'] . '">' . $item['list_title'] . '</dt>';
				echo '<dd>' . $item['list_content'] . '</dd>';
			}
			echo '</dl>';
		}
	}

	protected function _content_template() {
		?>
		<# if ( settings.list.length ) { #>
		<dl>
			<# _.each( settings.list, function( item ) { #>
				<dt class="elementor-repeater-item-{{ item._id }}">{{{ item.list_title }}}</dt>
				<dd>{{{ item.list_content }}}</dd>
			<# }); #>
			</dl>
		<# } #>
		<?php
	}
}

Complete List of Available Elementor Fields

Here’s a list of all available Elementor field controls:

  • Text — A simple text field.
  • Number — A simple number field.
  • Textarea — A textarea field.
  • WYSIWYG — The WordPress rich-text editor (TinyMCE).
  • Code — A code editor textarea based on Ace editor.
  • Hidden — A hidden input field in the panel, to save data in the database without an input field in the panel.
  • Switcher — A switcher control (on/off), a fancy representation of checkbox.
  • Popover Toggle — A toggle button to open and close a popover.
  • Select — A simple select box field.
  • Select2 — A select box field based on the Select2 plugin.
  • Choose — A radio buttons styled as groups of buttons with icons.
  • Color — A color picker field with an alpha slider.
  • Font — A font select box field based on Google Fonts library.
  • Date-Time — A date/time picker field based on the Flatpickr library.
  • Entrance Animation — An entrance animation select box field based on Animate.css library.
  • Hover Animation — A hover animation select box field based on Hover.css library.
  • Gallery — A gallery field based on the WordPress media library.
  • Repeater — Repeater controls allow you to build repeatable blocks of fields.

Add Custom Dynamic Tags/Field Values

A common need and important aspect of most forms it the ability to add dynamic content to the field values. Whether that’s to pre populated a user’s email address, add a hidden field with URL parameters, pass the post excerpt, post content, author info, archive title, site name, site logo or more.

Create a Custom Dynamic Tag

Start by creating a class that extends the Elementor\Core\DynamicTags\Tag class and fill in all the required methods. Each Dynamic Tag needs to have a few basic settings like a unique name, a title that will be used. On top of that, we have some advanced settings like the Dynamic Tag controls which are basically optional fields where the user can configure his custom data. And a render method that generates the final output based on the user settings from the Dynamic Tag’s controls.

Create the Dynamic Tag Structure

As mentioned above, Elementor Dynamic Tag extends the Elementor\Core\DynamicTags\Tag class and inherits its methods. A simple Dynamic Tag skeleton will look like this:

Class Elementor_Test_Tag extends \Elementor\Core\DynamicTags\Tag {
	public function get_name() {}

	public function get_title() {}

	public function get_group() {}

	public function get_categories() {}

	protected function _register_controls() {}

	public function render() {}
}

Let’s break it down:

  • get_name() – Return a Tag name (id) that will be used in the code .
  • get_title() – Return the Tag title that will be displayed as the Tag label.
  • get_group() – Return which group the Tag will appear under. (tag groups will be explained later on).
  • get_categories() – Return the controls categories the Tag belongs to. (tag categories will be explained later on).
  • _register_controls() – (Optional) Define Tag controls (setting fields) .
  • render() – The Tag output.

The Elementor\Core\DynamicTags\Tag class has many more methods you can use to do different things, but for now, this should be good enough.

Example of a Simple Dynamic Tag

To put all of the pieces together we are going to create a simple Elementor Dynamic Tag which will return a server variable.

Dynamic Tag Class

First, we need to create a class that extends the Elementor\Core\DynamicTags\Tag class:

Class Elementor_Server_Var_Tag extends \Elementor\Core\DynamicTags\Tag {
}
Dynamic Tag Settings

Now that we have a class for our dynamic tag, we can start filling in the methods, and we start with the simple ones:

Class Elementor_Server_Var_Tag extends \Elementor\Core\DynamicTags\Tag {
	public function get_name() {
		return 'server-variable';
	}

	public function get_title() {
		return __( 'Server Variable', 'elementor' );
	}

	public function get_group() {
		return 'request-variables';
	}

	public function get_categories() {
		return [ \Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY ];
	}
}

Side Note: By default, Elementor comes with these dynamic tag categories:

  • TEXT_CATEGORY – Dynamic tags text controls.
  • URL_CATEGORY – Dynamic tags URL controls.
  • IMAGE_CATEGORY – Dynamic tags image controls.
  • MEDIA_CATEGORY – Dynamic tags media controls.
  • POST_META_CATEGORY – Dynamic tags post meta controls.
  • GALLERY_CATEGORY – Dynamic tags gallery controls.
Dynamic Tag Controls

Next, we need to add the Dynamic Tag controls using the _register_controls() method:

Class Elementor_Server_Var_Tag extends \Elementor\Core\DynamicTags\Tag {
	protected function _register_controls() {
		$variables = [];
		foreach ( array_keys( $_SERVER ) as $variable ) {
			$variables[ $variable ] = ucwords( str_replace( '_', ' ', $variable ) );
     		}

		$this->add_control(
			'param_name',
			[
				'label'   => __( 'Param Name', 'elementor' ),
			        'type' => \Elementor\Controls_Manager::SELECT,
			        'options' => $variables,
			]
		);
	}
}

We add a single select control with the list of the sever variables to choose from. Note that this is just an example, and in a real-world use case you would probably want to exclude some of the server variables.

Dynamic Tag Value

Last, we need to implement the render() method, which takes the variable name the user selected in the control and returns the server corresponding variable value:

Class Elementor_Server_Var_Tag extends \Elementor\Core\DynamicTags\Tag {
	public function render() {
		$param_name = $this->get_settings( 'param_name' );

		if ( ! $param_name ) {
			return;
		}

		if ( ! isset( $_SERVER[ $param_name ] ) ) {
			return;
		}

		$value = $_SERVER[ $param_name ];
		echo wp_kses_post( $value );
	}
}

The render() method simply checks that a server variable with the selected param name exists and echo it to the buffer after some minimal escaping.

The Entire Code

Altogether the Dynamic tag class with some extra phpDocs should look something like this:

<?php
Class Elementor_Server_Var_Tag extends \Elementor\Core\DynamicTags\Tag {

	/**
	* Get Name
	*
	* Returns the Name of the tag
	*
	* @since 2.0.0
	* @access public
	*
	* @return string
	*/
	public function get_name() {
		return 'server-variable';
	}

	/**
	* Get Title
	*
	* Returns the title of the Tag
	*
	* @since 2.0.0
	* @access public
	*
	* @return string
	*/
	public function get_title() {
		return __( 'Server Variable', 'elementor-pro' );
	}
   
	/**
	* Get Group
	*
	* Returns the Group of the tag
	*
	* @since 2.0.0
	* @access public
	*
	* @return string
	*/
	public function get_group() {
		return 'request-variables';
	}

	/**
	* Get Categories
	*
	* Returns an array of tag categories
	*
	* @since 2.0.0
	* @access public
	*
	* @return array
	*/
	public function get_categories() {
		return [ \Elementor\Modules\DynamicTags\Module::TEXT_CATEGORY ];
	}

	/**
	* Register Controls
	*
	* Registers the Dynamic tag controls
	*
	* @since 2.0.0
	* @access protected
	*
	* @return void
	*/
	protected function _register_controls() {

		$variables = [];

		foreach ( array_keys( $_SERVER ) as $variable ) {

			$variables[ $variable ] = ucwords( str_replace( '_', ' ', $variable ) );
		}

		$this->add_control(
			'param_name',
			[
				'label' => __( 'Param Name', 'elementor-pro' ),
				'type' => \Elementor\Controls_Manager::SELECT,
				'options' => $variables,
			]
		);
	}

	/**
	* Render
	*
	* Prints out the value of the Dynamic tag
	*
	* @since 2.0.0
	* @access public
	*
	* @return void
	*/
	public function render() {
		$param_name = $this->get_settings( 'param_name' );

        	if ( ! $param_name ) {
			return;
		}

		if ( ! isset( $_SERVER[ $param_name ] ) ) {
			return;
		}
	
		$value = $_SERVER[ $param_name ];
		echo wp_kses_post( $value );
	}
}

Register the Dynamic Tag

So after we have our dynamic tag class ready all that we have to do is register the tag with Elementor’s Dynamic tag manager at the elementor/dynamic_tags/register_tags hook:

add_action( 'elementor/dynamic_tags/register_tags', function( $dynamic_tags ) {
	// In our Dynamic Tag we use a group named request-variables so we need 
	// To register that group as well before the tag
	\Elementor\Plugin::$instance->dynamic_tags->register_group( 'request-variables', [
		'title' => 'Request Variables' 
	] );

	// Include the Dynamic tag class file
	include_once( 'path/to/dynamic/tag/class/file' );

	// Finally register the tag
	$dynamic_tags->register_tag( 'Elementor_Server_Var_Tag' );
} );

Final Notes on Creating Dynamic Tags

  • You should only include the Dynamic Tag class on the elementor/dynamic_tags/register_tags hook to make sure Elementor’s autoloader loads all base classes for you.
  • For URL, Image, Media and Gallery controls, you need to extend Elementor\Core\DynamicTags\Data_Tag instead.
    There are many types of controls you can add to your widget to make it easier to configure. We have a separate section with a full list of available controls.

Attach JS & CSS Scripts to the Widget

Building sites with a component-based approach like Elementor widgets provides allows you to load JS & CSS scripts only when the widget is used on the page. This gives you a nice boost in performance since your pages are only loading what’s needed.

You accomplish this by first registering all of your JS & CSS files in the plugin.php file using the widget_scripts method:

public function widget_scripts() {
  wp_register_style( 'elementor-awesomesauce', plugins_url( '/assets/css/elementor-awesomesauce.css', __FILE__ ), [], '1.0.0' );
}

Next, you’ll need to enqueue the script when the widget appears on the page in the widgets/awesomesauce.php file using the get_style_depends and get_script_depends methods:

public function get_style_depends() {
  $styles = [ 'elementor-awesomesauce' ];

  return $styles;
}

public function get_script_depends() {
  $scripts = [];

  return $scripts;
}

Now your JS & CSS files will only load when needed… awesome sauce!

Did you find Build Custom Elementor Widgets useful? Get articles in your inbox.

…and don’t worry, I hate spam as much as you. Expect to hear from me at most once a week.

Latest Job Postings
Posted on Aug 9, 2020 at 9:17pm
Full-time
Los Angeles
Posted on Aug 9, 2020 at 9:15pm
Full-time
Los Angeles
Posted on Aug 9, 2020 at 1:19am
Littleton, CO
jobs by Indeed
Sponsored
Need help with website, a boost in ranking or online marketing? Contact me today for a free quote.
Sponsored
Sponsor my site by donating to help support open-source projects, like WordPress Zero Spam, Referrer Analytics & others.
Sponsored
Do you run a development agency, freelance developer or offer website services. Contact me today for information about advertising.
Sponsored
Do you have a great article to share you want to contribute? Contact me about your idea.
Sponsored
Sponsor my site by donating to help support open-source projects, like WordPress Zero Spam, Referrer Analytics & others.

27 Comments on “Build Custom Elementor Widgets”

# Jul 15, 2020

Thanks for this Ben. Got this up and running so quickly.
Cheers,

Ebrahim

# Jul 3, 2020

Hi,

This is just great! Well, I want to keep my widgets in a new section, not in “General” or “basic” . How can i create a new section at left?

John

# Jun 1, 2020

Hello!

I have to build a Elementor widget for a HTML5 audio player and I need to define a playlist of MP3 files.
I’ve seen many Elementor widgets which allow the user to define this type of playlist https://i.imgur.com/aQksBh1.jpg

Each playlist item has a set of properties (like title, url etc). Also, each playlist item can be duplicated and dragged & dropped to the desired position in the playlist. Of course you can add new playlist items or delete the existing ones.

Do you know how to create this type of playlist items?

Thank you!

# Apr 7, 2020

Please help me to create widget with repeater
EG: i have 1 title and 1 content for 1 section, and i want to repeat section.
Is this posible ?

Mark L Wood-Patrick

# Feb 25, 2020

Many thanks for the presentation, very helpful. I have a question about php code style. When I look at the page: https://developers.elementor.com/getting-started/ it understandably recommends using phpcs using the wordpress coding standard but when I run phpcs on the elementor source a few problems are consistently reported:

Class file names should be based on the class name with “class-” prepended. Expected class-control-button.php, but found button.php.

Missing file doc comment

Short array syntax is not allowed

I’m using the current WordPress rules as far as I can see. Do you know where I can I find a phpcs rule set which matches what the elementor team are doing in practice? When I look at a well used plugin that works with Elementor such as Ocean Extra I see a lot more violations. Any pointers would be much appreciated.

# Feb 27, 2020

That’s pretty common with WP plugins & themes. Unfortunately, it’s the wild west when it comes to code quality in WP plugins.

Mark L Wood-Patrick

# Feb 28, 2020

Is there a github repository with the code from this article. I’m seeing some phpcs issues in my build of this code and would appreciate your comments on the reported issues , which issues I should ignore and which should be fixed and best way to fix them

FOUND 10 ERRORS AND 2 WARNINGS AFFECTING 8 LINES
———————————————————————————————————————– 1 | ERROR | Class file names should be based on the class name with “class-” prepended. Expected
| | class-awesomesauce.php, but found awesomesauce.php.
1 | ERROR | Missing file doc comment
12 | ERROR | Inline comments must end in full-stops, exclamation marks, or question marks
15 | ERROR | Missing short description in doc comment
87 | WARNING | Method name “_register_controls” should not be prefixed with an underscore to indicate visibility 217 | ERROR | All output should be run through an escaping function (see the Security sections in the WordPress | | Developer Handbooks), found ‘$this’.
217 | ERROR | All output should be run through an escaping function (see the Security sections in the WordPress | | Developer Handbooks), found ‘$settings[‘title’]’.
218 | ERROR | All output should be run through an escaping function (see the Security sections in the WordPress | | Developer Handbooks), found ‘$this’.
218 | ERROR | All output should be run through an escaping function (see the Security sections in the WordPress | | Developer Handbooks), found ‘$settings[‘description’]’.
219 | ERROR | All output should be run through an escaping function (see the Security sections in the WordPress | | Developer Handbooks), found ‘$this’.
219 | ERROR | All output should be run through an escaping function (see the Security sections in the WordPress | | Developer Handbooks), found ‘$settings[‘content’]’.
232 | WARNING | Method name “_content_template” should not be prefixed with an underscore to indicate visibility

Brian D

# Dec 6, 2019

Built my first widget! Thanks for the help.

I have a question. I found the Elementor documentation on adding a new category, but can’t figure out in which file and where to place the code. Does anyone know?

# Feb 27, 2020

Put it in the Plugin class:

public function register_categories( $elements_manager ) {
    $elements_manager-&gt;add_category(
      'categorykey',
      [
        'title' =&gt; __( 'Category Name', 'textdomain' ),
        'icon' =&gt; 'fa fa-plug',
      ]
    );
  }

# Nov 21, 2019

Awsome stuff.
I was about to start on a basic plugin, then looked at the oceanwp elementor addons plugin which looked really complicated.
Then I found this which is obviously what they used as well and now have a well-structured custom widgets plugin.

Thank you!

Bigrezzzz

# Oct 13, 2019

hi thanks for everything it was so much helpful .. can you please help me i want to know how can i register style just like script

Alex

# Jan 17, 2020

Hello,
Good tutorial,
How can i add more widgets? Is there a way to make one file php for widget? Like elementor default have.

Or what i want to do is to add some extra options to actual elementor widgets, ex background color and hover etc.

Cheers

Craig Tommola

# Feb 13, 2020

Same here – I followed this to the T and searched the docs too … adding to the plugins.php file didn’t work as expected …

public function widget_styles() {
wp_register_style( ‘elementor-awesomesauce’, plugins_url( ‘/assets/css/awesomesauce.css’, __FILE__ ) );
}

add_action( ‘elementor/frontend/after_enqueue_styles’, [ $this, ‘widget_styles’ ] );

# Feb 27, 2020

In plugin.php, add this to the Plugin class:

/**
   * widget_scripts
   *
   * Load required plugin core files.
   *
   * @access public
   */
  public function widget_scripts() {
    $plugin = get_plugin_data( __FILE__, false, false );

    wp_register_style( 'your-style', plugin_dir_url( __FILE__  ) . 'assets/css/file.css', [], $plugin['Version'] );
    wp_register_script( 'your-script', plugin_dir_url( __FILE__ ) . 'assets/js/file.js', [ 'jquery' ], $plugin['Version'], true );
  }

Then in your widget class add the following to call the scripts when the widget is loaded:

public function get_style_depends() {
    $styles = [ 'your-style' ];

    return $styles;
  }

  public function get_script_depends() {
    $scripts = [ 'your-script' ];

    return $scripts;
  }
Sanjaya Dulal

# Sep 30, 2019

Not working. I tired all above code .

Well how can i implement it. Any idea ?

i am bit confusion it.

Sanjaya Dulal

# Sep 30, 2019

Now i got it

Himanshu

# Jul 17, 2019

Wow , it worked perfectly ! Can you come up with any idea to register Custom Widget for “Single page” editor only ? I want to register a widget which will appear only in “Single” Editor .. Is it possible ?

# Aug 21, 2019

If you’re trying to conditionally show/hide a widget in the frontend, try hooking into ‘elementor/widget/render_content’ .
See https://code.elementor.com/php-hooks/#elementorwidgetrender_content

If you’re trying to restrict certain widgets in the editor, I don’t think (maybe) there’s an action for that. Elementor provides a ‘Role Manager’ though. In the Pro version, there’s an option to ‘only allow them to edit existing content inside the editor’. This means you can publish/create a template beforehand, then whoever edits that page/template, can only edit the existing content in the editor (only the widgets you used for that template).

Abe Caymo

# May 15, 2019

Hey thanks for this great article btw!

Could you add an example on responsive controls?
More specifically, in the ‘Responsive Choose’ found in the https://developers.elementor.com/add-responsive-controls-to-widgets/ , what is the “prefix_class” used for?

# Feb 27, 2020

That’s used to create a prefix for the alignment class. In their example, depending on the choice, the final class could be: content-align-left, content-align-right or content-align-center

Abe Caymo

# May 10, 2019

Just wow. I’m relatively new to coding and after going through this tutorial, now I understand what ‘good code documents itself’ means. Now I ACTUALLY understand more about OOP seeing it in action. Thanks a lot!

Eric

# May 2, 2019

This is really great! One thing I’m trying to do is create a custom Elementor widget that just runs some PHP where it is placed. Do you know if this is possible? I’ve been tinkering around but can’t quite get it to work. I actually don’t need any controls for the widget– it’s so I can place a custom action hook within my Elementor pages.

John

# Jul 30, 2019

I’m sure there’s a plugin that enables PHP in the WP text widget. It might be possible to do it that way?

# Feb 27, 2020

May not be understanding, but likely you don’t need the elementor widget. You just need to add your code into a core WP hook like init or wp. Which hook you choose will depend on your needs. You could also potentially create a simple shortcode that you can add into Elementor pages using the shortcode widget which can run anything you’d like with no output.

Michael Bourne

# Apr 26, 2019

This is better than the official docs. Kudos.

Giovani Camara

# Mar 19, 2019

This was ridiculously useful… I managed to get this all working which is great! Do you know if this awesomesauce plugin can only have one extension, or is it possible to have multiple widgets in this individual plugin? I renamed it all appropriate to my project, but I am having difficulties adding more widgets to it…. any ideas?

# Mar 20, 2019

Glad you found it useful any Yup, you can add as many as you like! Look in plugin.php in the register_widgets method. Just follow the example for a new widget, then register it. Ex:

private function include_widgets_files() {
  // First widget
  require_once( __DIR__ . '/widgets/awesomesauce.php' );
  // Second widget
  require_once( __DIR__ . '/widgets/awesomesauce2.php' );
}

public function register_widgets() {
  // Its is now safe to include Widgets files
  $this-&gt;include_widgets_files();
 
  // Register Widgets
  // First widget
  \Elementor\Plugin::instance()-&gt;widgets_manager-&gt;register_widget_type( new Widgets\Awesomesauce() );
  // Second widget
  \Elementor\Plugin::instance()-&gt;widgets_manager-&gt;register_widget_type( new Widgets\Awesomesauce2() );
}

Leave a Reply

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

All comments posted on 'Build Custom Elementor Widgets' are held for moderation and only published when on topic and not rude. Get a gold star if you actually read & follow these rules.

You may write comments in Markdown. This is the best way to post any code, inline like `<div>this</div>` or multiline blocks within triple backtick fences (```) with double new lines before and after.

Want to tell me something privately, like pointing out a typo or stuff like that? Contact Me.