Projects Engine - WordPress

How to Create a Custom Widget in Elementor

Elementor is a versatile page builder for WordPress with a lot of widgets straight out of the box.

Home / Blog / Elementor / How to Create a Custom Widget in Elementor

Elementor is a versatile page builder for WordPress with a lot of widgets straight out of the box. Not only that, but it also has a simple developer API that helps you to expand its features and make custom Elementor widgets. We’ll go into how to build an Elementor widget in this post.

Instructions Step-by-Step

With this easy-to-follow, detailed guide, you can build an Elementor widget and expand its features. Find a long list of available tabs, sections, and fields — the only limit is your imagination.

1. Setup a custom plugin.

Use a separate plugin to store your custom Elementor widgets. This enables you to monitor and upgrade it without being reliant on the theme in which it is used.

2. Define the plugin.

You must identify every WordPress plugin so that WordPress can recognize it and enable it in the admin dashboard.

3. Initialize the plugin.

This is where you’ll verify that the PHP, WordPress, and Elementor versions follow the plugin’s minimum specifications.

4. Register a custom category

Register a custom category. Also, in this step we will register the scripts and the files for the widgets.

5. Build the custom widget.

In this step, you’ll add the coding that will make the widget available in both the editor and the tab it’s on. This is where you’ll set up the tabs, sections, and fields for the widget.

Step 1: Setup a custom plugin

Let’s begin by configuring the custom plugin, let’s call it “projects-engine“.

  1. Create a directory called “projects-engine” in “wp-content/plugins“.
  2. In the plugin directory, create the following files & directories:
projects-engine.php
elementor/class-elementor.php
elementor/class-widgets.php
elementor/widgets/class-widget-name.php
elementor/assets/js/projects-engine.js
elementor/assets/css/projects-engine.css

Step 2: Define the plugin

We’ll describe the plugin in the “projects-engine.php” file so WordPress knows it and enables you to use it in the admin dashboard.

<?php
/**
 * Projects Engine
 *
 * @copyright Copyright (C) 2021-2021, Projects Engine - [email protected]
 * @license   http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License, version 3 or higher
 *
 * @wordpress-plugin
 * Plugin Name: Projects Engine
 * Version:     1.9.2
 * Plugin URI:  https://projectsengine.com/plugins/projects-engine
 * Description: The first true all-in-one solutions manager for WordPress, including page content creation, plugin development, sitemaps and much more.
 * Author:      Projects Engine
 * Author URI:  https://projectsengine/author/dragipostolovski
 * Text Domain: projects-engine-plugin
 * Domain Path: /languages/
 * License:     GPL v3
 * Requires at least: 5.5
 * Requires PHP: 7.3.9
 *
 * PEP requires at least: 3.0
 * PEP tested up to: 5.1
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

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

define( 'PE_PLUGIN_DOMAIN', 'projects-engine' );
define( 'PE_PLUGIN_VERSION', '1.9.2');
define( 'PE_PLUGIN_PATH', plugin_dir_path( __FILE__ ) );
define( 'PE_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
define( 'MINIMUM_ELEMENTOR_VERSION', '2.0.0' );
define( 'MINIMUM_PHP_VERSION', '7.0' );

require PE_PLUGIN_PATH . '/gutenberg/main.php';
require PE_PLUGIN_PATH . '/includes/main.php';
require PE_PLUGIN_PATH . '/woocommerce/main.php';
require PE_PLUGIN_PATH . '/elementor/class-elementor.php';

add_action( 'after_setup_theme', 'pe_theme_setup' );
/**
 * Add woocommerce support to your theme.
 */
function pe_theme_setup() {
	add_theme_support( 'woocommerce' );
}

Step 3: Initialize the plugin

We’ll configure the plugin and ensure that PHP, WordPress, and Elementor version specifications are fulfilled in the “class-elementor.php” format.

<?php

/**
 * PE_Elementor class.
 *
 * @category   Class
 * @package    PEElementor
 * @subpackage WordPress
 * @author     Dragi Postolovski <[email protected]>
 * @copyright  2021 Dragi Postolovski
 * @license    https://opensource.org/licenses/GPL-3.0 GPL-3.0-only
 * @link       link(https://projectsengine.com/plugins/projects-engine,
 *             Build Custom Elementor Widgets)
 * @since      1.9.2
 * php version 7.3.9
 */

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

/**
 * PE_Elementor class.
 *
 * The init class that runs the Elementor plugin.
 * You should only modify the constants to match your plugin's needs.
 */
final class PE_Elementor {
	/**
	 * PE_Elementor constructor.
	 *
	 * @since 1.9.2
	 * @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 plugin localization files.
	 * Fired by `init` action hook.
	 *
	 * @since 1.9.2
	 * @access public
	 */
	public function i18n() {
		load_plugin_textdomain( PE_PLUGIN_DOMAIN );
	}

	/**
	 * 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.9.2
	 * @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, 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, MINIMUM_PHP_VERSION, '<' ) ) {
			add_action( 'admin_notices', array( $this, 'admin_notice_minimum_php_version' ) );
			return;
		}

		// We have passed all validation checks and now we can safely include our widgets.
		require_once PE_PLUGIN_PATH. 'elementor/class-widgets.php';
	}

	/**
	 * Warning when the site doesn't have Elementor installed or activated.
	 *
	 * @since 1.9.2
	 * @access public
	 */
	public function admin_notice_missing_main_plugin() {
		deactivate_plugins( plugin_basename( PE_FILE ) );

		if ( isset( $_GET['activate'] ) ) unset( $_GET['activate'] );

		$message = sprintf(
			esc_html__( '"%1$s" requires "%2$s" to be installed and activated.', PE_PLUGIN_DOMAIN ),
			'<strong>' . esc_html__( 'Projects Engine', PE_PLUGIN_DOMAIN ) . '</strong>',
			'<strong>' . esc_html__( 'Elementor', PE_PLUGIN_DOMAIN ) . '</strong>'
		);

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

	/**
	 * Warning when the site doesn't have a minimum required Elementor version.
	 *
	 * @since 1.9.2
	 * @access public
	 */
	public function admin_notice_minimum_elementor_version() {
		deactivate_plugins( plugin_basename( PE_FILE ) );

		if ( isset( $_GET['activate'] ) ) unset( $_GET['activate'] );

		$message = sprintf(
			esc_html__( '"%1$s" requires "%2$s" version %3$s or greater.', PE_PLUGIN_DOMAIN ),
			'<strong>' . esc_html__( 'Projects Engine', PE_PLUGIN_DOMAIN ) . '</strong>',
			'<strong>' . esc_html__( 'Elementor', PE_PLUGIN_DOMAIN ) . '</strong>',
			'<strong>' . esc_html__( MINIMUM_ELEMENTOR_VERSION, PE_PLUGIN_DOMAIN ) . '</strong>',
		);

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

	/**
	 * Warning when the site doesn't have a minimum required PHP version.
	 *
	 * @since 1.9.2
	 * @access public
	 */
	public function admin_notice_minimum_php_version() {
		deactivate_plugins( plugin_basename( PE_FILE ) );

		if ( isset( $_GET['activate'] ) ) unset( $_GET['activate'] );

		$message = sprintf(
			esc_html__( '"%1$s" requires "%2$s" version %3$s or greater.', PE_PLUGIN_DOMAIN ),
			'<strong>' . esc_html__( 'Projects Engine', PE_PLUGIN_DOMAIN ) . '</strong>',
			'<strong>' . esc_html__( 'PHP', PE_PLUGIN_DOMAIN ) . '</strong>',
			'<strong>' . esc_html__( MINIMUM_PHP_VERSION, PE_PLUGIN_DOMAIN ) . '</strong>',
		);

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

// Instantiate PE_Elementor.
new PE_Elementor;

Step 4: Register a custom category

In this step, we will create a custom widget category for the Elementor Dashboard. Here will be visible all of our widgets

<?php

// We check if the Elementor plugin has been installed / activated.
if ( ! in_array( 'elementor/elementor.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) {
	return;
}

/**
 * Include the widgets and create a custom category.
 *
 * @package PEElementor
 *
 * @since 1.9.2
 */
class PE_Elementor_Widget {

	private static $instance = null;

	/**
	 * @since 1.9.2
	 */
	public static function get_instance() {
		if ( ! self::$instance ) {
			self::$instance = new self;
		}

		return self::$instance;
	}

	/**
	 * @since 1.9.2
	 */
	public function init() {
		add_action( 'elementor/widgets/widgets_registered', array( $this, 'widgets_registered' ) );

		add_action( 'elementor/frontend/after_register_scripts', array( $this, 'register_frontend_scripts' ), 10 );

		add_action( 'elementor/frontend/after_register_styles', array( $this, 'register_frontend_styles' ), 10 );

		add_action( 'elementor/elements/categories_registered', array( $this, 'elementor_widget_categories' ) );
	}

	/**
	 * Register the widgets.
	 *
	 * @since 1.9.2
	 */
	public function widgets_registered() {
		//Require all PHP files in the /elementor/widgets directory
		foreach ( glob( PE_PLUGIN_PATH . "elementor/widgets/*.php" ) as $file ) {
			require $file;
		}
	}

	/**
	 * Register the scripts.
	 *
	 * @since 1.9.2
	 */
	public function register_frontend_scripts() {
		wp_enqueue_script( 'projects-engine', PE_PLUGIN_URL . 'elementor/assets/js/projects-engine.js', array( 'jquery' ), false, true );
	}

	/**
	 * Register the styles.
	 *
	 * @since 1.9.2
	 */
	public function register_frontend_styles() {
		wp_enqueue_style( 'projects-engine', PE_PLUGIN_URL . 'elementor/assets/css/projects-engine.css', null, 1.9 );
	}


	/**
	 * Custom elementor dashboard widgets category.
	 * The widgets will be visible here.
	 *
	 * @since 1.9.2
	 *
	 * @param $elements_manager
	 */
	public function elementor_widget_categories( $elements_manager ) {

		$elements_manager->add_category(
			'pe-category',
			[
				'title' => esc_html__( 'Projects Engine', PE_PLUGIN_DOMAIN ),
				'icon'  => 'fa fa-plug',
			]
		);
	}
}

PE_Elementor_Widget::get_instance()->init();

Step 5: Build the custom widget

We’ll build the custom Elementor widget in the “widgets/class-widget-name.php” file. It will show the six latest published posts.

<?php

use Elementor\Controls_Manager;
use Elementor\Group_Control_Background;
use Elementor\Plugin;
use Elementor\Widget_Base;

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

class Latest_Posts_Widget extends Widget_Base {

	/**
	 * Get the widget's name.
	 *
	 * @return string
	 */
	public function get_name(): string {
		return 'pe-latest-posts';
	}

	/**
	 * Get the widget's title.
	 *
	 * @return string
	 */
	public function get_title(): string {
		return esc_html__( 'PE Latest Posts', PE_PLUGIN_DOMAIN );
	}

	/**
	 * Get the widget's icon.
	 *
	 * @return string
	 */
	public function get_icon(): string {
		return 'fa fa-clipboard';
	}

	/**
	 * Add the widget to a category.
     * Previously setup in the class-widgets.php file.
	 *
	 * @return string[]
	 */
	public function get_categories(): array {
		return [ 'pe-category' ];
	}


	protected function register_controls() {
		$this->start_controls_section(
			'content_section',
			[
				'label' => esc_html__( 'Latest Posts', PE_PLUGIN_DOMAIN ),
				'tab' => Controls_Manager::TAB_CONTENT,
			]
		);

		$this->add_group_control(
			Group_Control_Background::get_type(),
			[
				'name' => 'background',
				'label' => __( 'Background', PE_PLUGIN_DOMAIN ),
				'types' => [ 'classic', 'gradient', 'video' ],
				'selector' => '{{WRAPPER}} .pe-wrapper-latest-posts',
			]
		);

		$this->add_control(
			'pe-posts-order',
			[
				'label' => __( 'Order', PE_PLUGIN_DOMAIN ),
				'type' => Controls_Manager::SELECT,
				'multiple' => false,
				'default' => 'DESC',
				'options' => [
					'ASC' => 'ASC',
					'DESC' => 'DESC',
				]
			]
		);

		$this->add_control(
			'pe-no-posts-message',
			[
				'label' => __( 'No post message', 'bco-core' ),
				'type' => Controls_Manager::TEXT,
				'default' => 'There are no posts at the moment.'
			]
		);

		$this->end_controls_section();
	}

	protected function render() {
		$order = $this->get_settings_for_display('pe-posts-order');
		$message = $this->get_settings_for_display('pe-no-posts-message');

		if (Plugin::$instance->editor->is_edit_mode()) {
			// If the Elementor editor is opened.

		}

		$args = array(
			'post_type' => 'post',
			'post_status' => 'publish',
			'posts_per_page' => 6,
			'orderby' => 'date',
			'order' => $order,
		);

		$cpt = new WP_Query($args);
		$posts = $cpt->posts;

		if($posts) : ?>
		    <div class="pe-wrapper-latest-posts">
			<?php foreach ($posts as $post ) { ?>
			    <div class="pe-latest-post">
				<h1><?= $post->post_title; ?></h1>
			    </div>
			<?php } ?>
		    </div>
		<?php else: ?>
		    <div class="pe-wrapper-latest-posts">
			<p><?= $message; ?></p>
		    </div>
		<?php endif;

    	}
}

Plugin::instance()->widgets_manager->register_widget_type( new Latest_Posts_Widget() );

Available widget field options

Elementor is extremely powerful, allowing you to add custom tabs, fields, style settings, and even responsiveness with ease. We’ve already added a few fields for title, and content in the “register_controls” method of the code above, but what if you need more? Check out the samples below to make your plugin even more awesome.

Widget Tabs

Tabbed sections may be added to Elementor widgets. The Content, Style, and Advanced tabs, for example, are present in many Elementor core widgets. You may add custom fields and other content to each widget instance on a page using the tabbed sections.

Widget Content Tab

$this->start_controls_section(
	'content_section',
	[
		'label' => esc_html__( 'Latest Posts', PE_PLUGIN_DOMAIN ),
		'tab' => Controls_Manager::TAB_CONTENT,
	]
);

$this->end_controls_section();

Widget Image Field

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

Widget Select Dropdown

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

List of Available Elementor Fields

The following is a list of all Elementor field controls:

  • Text – text field.
  • Number – number field.
  • Textarea – Textarea field.
  • WYSIWYG – WordPress rich-text editor (TinyMCE).
  • Code – Textarea based on Ace editor.
  • Hidden – hidden input field in the panel
  • Switcher – fancy representation of checkbox.
  • Popover Toggle – toggle button
  • Select – select box field.
  • Select2 – based on the Select2 plugin.
  • Choose – radio buttons
  • Color – color picker field.
  • Font – based on Google Fonts library
  • Date-Time – date/time picker field
  • Entrance Animation — animation select box field
  • Hover Animation – hover animation select box field
  • Gallery – media library
  • Repeater – repeatable blocks of fields

In conclusion

  • get_name() – returns id that will be used in the code.
  • get_title() – title that will be displayed as the widget label
  • get_icon() – Return the widget icon, you can use font awesome icons as well
  • get_categories() – where you would like your widget to appear
  • register_controls() – define controls (setting fields)
  • render() – output

Frequently Asked Questions

How can you create a custom Elementor widget?

Using the Elementor Developer API, you can quickly build a custom Elementor widget. This article is great option to start building your own custom widget.

Where should Elementor widgets be created?

In the scope of a plugin. Elementor widgets can never be created inside a theme. They can be self-contained and stand-alone modules that can be used independently of the theme.

Your creativity is the only limit on what you can do with Elementor. What kind of cool widgets have you made? Let us know how you’ve used custom Elementor widgets on your site in the comments section below.

Comments

At the moment, there are 2 comments for this post.

Thank you!) p.s. you have error in file naming list projects-engine.php elementor/class-elementor.php elementor/cllass-widgets.php .... cllass -
_register_controls() this function is deprecated you need to change it by register_controls() function -