Create an Elementor Widget

Written by
Published
Updated
Typical Read
22 minutes

Create an Elementor widget and extend its functionality with this easy-to-follow, definitive guide. Find an extensive list of available tabs, sections, and fields — your imagination is the only limitation.

Create an Elementor widget

tl;dr

Create an Elementor Widget Guide

Out-of-the-box, Elementor is a powerful page builder, packed with a ton of widgets. Not only that but also provides an easy-of-use developer API allowing you to extend its functionality and build custom Elementor widgets. In this article, we’ll go over how to create an Elementor widget.

Step-by-Step Instructions

  1. Setup a custom plugin.

    Always add your custom Elementor widgets in an independent plugin. This allows you to easily manage and update it without being dependent on the theme it’s used in.

  2. Define the plugin.

    For any WordPress plugin, you’ll need to define it so WordPress recognizes and allows you to enable it in the admin dashboard.

  3. Initialize the plugin.

    This is where you’ll check the PHP, WordPress, and Elementor versions to ensure the minimum requirements are met for the plugin.

  4. Build the custom widget.

    Now you’ll add the code that’ll display the widget both in the editor and page it’s added on. This is where you’ll define its tabs, sections, and fields.

Bonus: Create an Elementor widget with dynamic fields.

One of the many powerful features of Elementor, is its ability to pre-populate fields dynamically (i.e. user’s email, post name, custom meta field, etc.). The pre-baked dynamic fields will handle 95% of use cases, however you may need to extend them to add your own.

Creating custom dynamic field options (aka. tags) is a cinch and can be added to existing widgets or when you create an Elementor widget below.

How to Create an Elementor Widget

Let’s dive into the code. We’ll create an Elementor widget called Awesomesauce that’ll have a title and content field. You can use it as a template to create your own Elementor widgets.

Step 1. Setup the plugin.

To begin let’s start with setting up the Awesomesauce custom plugin:

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

Step 2. Define the plugin.

In the elementor-awesomesauce.php file, we’ll define the plugin so WordPress recognizes and allows you to enable in the admin dashboard.

elementor-awesomesauce.php

<?php
/**
 * Elementor Awesomesauce WordPress Plugin
 *
 * @package ElementorAwesomesauce
 *
 * Plugin Name: Elementor Awesomesauce
 * Description: Simple Elementor plugin example
 * Plugin URI:  https://www.benmarshall.me/build-custom-elementor-widgets/
 * Version:     1.0.0
 * Author:      Ben Marshall
 * Author URI:  https://www.benmarshall.me
 * Text Domain: elementor-awesomesauce
 */
define( 'ELEMENTOR_AWESOMESAUCE', __FILE__ );
/**
 * Include the Elementor_Awesomesauce class.
 */
require plugin_dir_path( ELEMENTOR_AWESOMESAUCE ) . 'class-elementor-awesomesauce.php';

Step 3. Initialize the plugin.

In the class-elementor-awesomesauce.php file, we’ll initialize the plugin and verify the PHP, WordPress, and Elementor version requirements are met.

<?php
/**
 * Elementor_Awesomesauce class.
 *
 * @category   Class
 * @package    ElementorAwesomesauce
 * @subpackage WordPress
 * @author     Ben Marshall <me@benmarshall.me>
 * @copyright  2020 Ben Marshall
 * @license    https://opensource.org/licenses/GPL-3.0 GPL-3.0-only
 * @link       link(https://www.benmarshall.me/build-custom-elementor-widgets/,
 *             Build Custom Elementor Widgets)
 * @since      1.0.0
 * php version 7.3.9
 */
if ( ! defined( 'ABSPATH' ) ) {
	// Exit if accessed directly.
	exit;
}
/**
 * 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 class-widgets.php file.
 */
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 the translation.
		add_action( 'init', array( $this, 'i18n' ) );
		// Initialize the plugin.
		add_action( 'plugins_loaded', array( $this, 'init' ) );
	}
	/**
	 * Load Textdomain
	 *
	 * Load plugin localization files.
	 * Fired by `init` action hook.
	 *
	 * @since 1.0.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.0.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 widgets.
		require_once 'class-widgets.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() {
		deactivate_plugins( plugin_basename( ELEMENTOR_AWESOMESAUCE ) );
		return sprintf(
			wp_kses(
				'<div class="notice notice-warning is-dismissible"><p><strong>"%1$s"</strong> requires <strong>"%2$s"</strong> to be installed and activated.</p></div>',
				array(
					'div' => array(
						'class'  => array(),
						'p'      => array(),
						'strong' => array(),
					),
				)
			),
			'Elementor Awesomesauce',
			'Elementor'
		);
	}
	/**
	 * 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() {
		deactivate_plugins( plugin_basename( ELEMENTOR_AWESOMESAUCE ) );
		return sprintf(
			wp_kses(
				'<div class="notice notice-warning is-dismissible"><p><strong>"%1$s"</strong> requires <strong>"%2$s"</strong> version %3$s or greater.</p></div>',
				array(
					'div' => array(
						'class'  => array(),
						'p'      => array(),
						'strong' => array(),
					),
				)
			),
			'Elementor Awesomesauce',
			'Elementor',
			self::MINIMUM_ELEMENTOR_VERSION
		);
	}
	/**
	 * 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() {
		deactivate_plugins( plugin_basename( ELEMENTOR_AWESOMESAUCE ) );
		return sprintf(
			wp_kses(
				'<div class="notice notice-warning is-dismissible"><p><strong>"%1$s"</strong> requires <strong>"%2$s"</strong> version %3$s or greater.</p></div>',
				array(
					'div' => array(
						'class'  => array(),
						'p'      => array(),
						'strong' => array(),
					),
				)
			),
			'Elementor Awesomesauce',
			'Elementor',
			self::MINIMUM_ELEMENTOR_VERSION
		);
	}
}
// Instantiate Elementor_Awesomesauce.
new Elementor_Awesomesauce();

Step 4. Build the custom widget.

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

widgets/class-awesomesauce.php

<?php
/**
 * Awesomesauce class.
 *
 * @category   Class
 * @package    ElementorAwesomesauce
 * @subpackage WordPress
 * @author     Ben Marshall <me@benmarshall.me>
 * @copyright  2020 Ben Marshall
 * @license    https://opensource.org/licenses/GPL-3.0 GPL-3.0-only
 * @link       link(https://www.benmarshall.me/build-custom-elementor-widgets/,
 *             Build Custom Elementor Widgets)
 * @since      1.0.0
 * php version 7.3.9
 */
namespace ElementorAwesomesauce\Widgets;
use Elementor\Widget_Base;
use Elementor\Controls_Manager;
// Security Note: Blocks direct access to the plugin PHP files.
defined( 'ABSPATH' ) || die();
/**
 * Awesomesauce widget class.
 *
 * @since 1.0.0
 */
class Awesomesauce extends Widget_Base {
	/**
	 * Class constructor.
	 *
	 * @param array $data Widget data.
	 * @param array $args Widget arguments.
	 */
	public function __construct( $data = array(), $args = null ) {
		parent::__construct( $data, $args );
		wp_register_style( 'awesomesauce', plugins_url( '/assets/css/awesomesauce.css', ELEMENTOR_AWESOMESAUCE ), array(), '1.0.0' );
	}
	/**
	 * Retrieve the widget name.
	 *
	 * @since 1.0.0
	 *
	 * @access public
	 *
	 * @return string Widget name.
	 */
	public function get_name() {
		return 'awesomesauce';
	}
	/**
	 * Retrieve the widget title.
	 *
	 * @since 1.0.0
	 *
	 * @access public
	 *
	 * @return string Widget title.
	 */
	public function get_title() {
		return __( 'Awesomesauce', 'elementor-awesomesauce' );
	}
	/**
	 * Retrieve the widget icon.
	 *
	 * @since 1.0.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.0.0
	 *
	 * @access public
	 *
	 * @return array Widget categories.
	 */
	public function get_categories() {
		return array( 'general' );
	}
	
	/**
	 * Enqueue styles.
	 */
	public function get_style_depends() {
		return array( 'awesomesauce' );
	}
	/**
	 * Register the widget controls.
	 *
	 * Adds different input fields to allow the user to change and customize the widget settings.
	 *
	 * @since 1.0.0
	 *
	 * @access protected
	 */
	protected function _register_controls() {
		$this->start_controls_section(
			'section_content',
			array(
				'label' => __( 'Content', 'elementor-awesomesauce' ),
			)
		);
		$this->add_control(
			'title',
			array(
				'label'   => __( 'Title', 'elementor-awesomesauce' ),
				'type'    => Controls_Manager::TEXT,
				'default' => __( 'Title', 'elementor-awesomesauce' ),
			)
		);
		$this->add_control(
			'description',
			array(
				'label'   => __( 'Description', 'elementor-awesomesauce' ),
				'type'    => Controls_Manager::TEXTAREA,
				'default' => __( 'Description', 'elementor-awesomesauce' ),
			)
		);
		$this->add_control(
			'content',
			array(
				'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.0.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 wp_kses( $settings['title'], array() ); ?></h2>
		<div <?php echo $this->get_render_attribute_string( 'description' ); ?><?php echo wp_kses( $settings['description'], array() ); ?></div>
		<div <?php echo $this->get_render_attribute_string( 'content' ); ?><?php echo wp_kses( $settings['content'], array() ); ?></div>
		<?php
	}
	/**
	 * Render the widget output in the editor.
	 *
	 * Written as a Backbone JavaScript template and used to generate the live preview.
	 *
	 * @since 1.0.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
	}
}

Register the widgets & scripts.

Each Elementor widget can have it’s own CSS & JS files that’ll be loaded conditionally if it appears on the page. This is an important aspect of the component-based approach that helps boost site performance so only the resources needed are loaded.

In our example widget, we’ve registered all styles and scripts in the widget’s class constructor:

class-awesomesauce.php

/**
 * Class constructor.
 *
 * @param array $data Widget data.
 * @param array $args Widget arguments.
 */
public function __construct( $data = array(), $args = null ) {
	parent::__construct( $data, $args );
	wp_register_style( 'awesomesauce', plugins_url( '/assets/css/accordion.css', ELEMENTOR_AWESOMESAUCE ), array(), '1.0.0' );
}

Available widget field options.

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.

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:

$this->add_control(
	'border_style',
	array
		'label'   => __( 'Border Style', 'plugin-domain' ),
		'type'    => \Elementor\Controls_Manager::SELECT,
		'default' => 'solid',
		'options' => array(
			'solid'  => __( 'Solid', 'plugin-domain' ),
			'dashed' => __( 'Dashed', 'plugin-domain' ),
			'dotted' => __( 'Dotted', 'plugin-domain' ),
			'double' => __( 'Double', 'plugin-domain' ),
			'none'   => __( 'None', 'plugin-domain' ),
		),
	)
);

Next you’ll need to render the select output with the render method:

protected function render() {
  $settings = $this->get_settings_for_display();
  echo '&lt;div style="border-style: ' . esc_attr( $settings['border_style'] ) . '"> .. &lt;/div>';
}

Lastly, you’ll want to create the template Elementor uses when in the editor mode in the _content_template method:

protected function _content_template() {
	?>
	&lt;div style="border-style: {{ settings.border_style }}"> .. &lt;/div>
	&lt;?php
}

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.

Adding Elementor Widget CSS & JS

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 class-widgets.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!

Create Conditional Fields/Controls

What about conditional fields? Elementor can handle that too. It’s as easy as using the condition argument when creating a control:

'condition' => array(
  'fieldkey' => 'value',
),

For more information on conditional fields and more advanced usage, see the Conditional Elementor Controls post.

Create Custom Dynamic Field/Tag 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() – Returns a Tag name (id) that will be used in the code.
  • get_title() – 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 control 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.

FAQ

How do you create an Elementor widget?

Creating a custom Elementor widget is easy using the Elementor Developer API. The Awesomesauce plugin in this article is a great

Where should Elementor widgets be created?

In a plugin. Never create Elementor widgets within a theme. They should be independent and a stand-alone component that can be used anywhere regardless of the theme it’s in.

How do you create conditional Elementor widget fields?

Use the condition argument when defining your control. For more information, see the Conditional Elementor Controls post.

In Conclusion

Using the Elementor Developer API allows you to create an Elementor widget for anything you may need. It can be as simple as the Awesomesauce plugin we built in this article or something as complicated as the Pardot Form Handler widget that includes conditional and custom fields along with advanced styling configuration in the Pardot Marketing plugin.

The only limit to what you can do with Elementor is your imagination. What awesomesauce widgets have you created? Comment below with how you’ve implemented custom Elementor widgets on your site.

34 comments on “Create an Elementor Widget”.

# Nov 25, 2020

Hi Ben,

First of all thank you for this great example. This really helpt with creating my own plugin. The only thing that doesn’t work is adding a custom category. I have read one of your comments which tells that i have to put the following code into the Widgets class:

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

But unfortunately, this doesn’t work. I have also read the Elementor documentary but with that, I also don’t get any further.
Maybe you can point me in the right direction.

# Dec 15, 2020

Hey Roelof, make sure you call the method in the constructor of the class:
// Add widget categories. add_action( 'elementor/elements/categories_registered', array( $this, 'register_categories' ) );

Mike

# Oct 27, 2020

Like everyone else said – wow! Thanks so much Ben.

Just one observation / correction. It looks like you accidentally used the same code under your "Elementor Widget Select Dropdown" section as you did under your "Elementor Widget Image Field" section.

https://i.postimg.cc/13bGMWsB/widget-select-dropdown-wrong-code.jpg

Could you re-visit and correct the code example?

# Dec 15, 2020

Thanks for the catch! Sorry about that. I’ve updated it accordingly.

Yoni

# Oct 21, 2020

I get no errors, but my widget does not show up in the Elementor panel in the editor.

I am trying to mimick the Elementor Login Form as I want to make changes (and they don’t offer override options to their widgets)

Can you possibly suggest what the problem might be?

# Dec 15, 2020

Are you seeing anything in the error logs? Might try to just download the example repo (https://github.com/bmarshall511/elementor-awesomesauce) and modify from there.

# 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 (PHP_CodeSniffer) using the WordPress coding standards, 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 find a phpcs rule set which matches what the Elementor team is 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

# Aug 16, 2020

Hey Mark, thanks for that info. I went ahead and created a repo for it:

https://github.com/bmarshall511/elementor-awesomesauce

Regarding phpcs, you’re absolutely right. I went ahead and updated the post using the WordPress code sniffer standard. Everything validates now, minus the get_render_attribute_string calls in the class-awesomesauce.php file. Complains about that content needing to be escaped, but haven’t had a chance to look into the best approach there.

You’re also right that Elementor itself doesn’t follow every rule using the WordPress standard, most development shops have some variation they prefer. You might try contacting Elementor to see if there’s a specific one they implement.

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 Widgets class:

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

Then call the method in the constructor:

// Add widget categories.
add_action( 'elementor/elements/categories_registered', array( $this, 'register_categories' ) );

# 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 class-widgets.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', array( $this, 'widget_styles' ) );

# Feb 27, 2020

In class-widgets.php, add this to the Widgets 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', array(), $plugin['Version'] );
wp_register_script( 'your-script', plugin_dir_url( __FILE__ ) . 'assets/js/file.js', array( '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 = array( 'your-style' );

return $styles;
}

public function get_script_depends() {
$scripts = array( '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 class-widgets.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->include_widgets_files();

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

Join the conversation.

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

All comments posted on 'Create an Elementor Widget' 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.