Building a Custom Widget for Elementor: Step-by-Step Guide

Elementor is one of the most popular page builders for WordPress. It offers a wide range of pre-built widgets. But what if you need something unique that isn’t available out of the box? We’ll show you how to create a custom Elementor widget from scratch. By the end, you’ll be able to add fully personalized elements to your pages, tailored exactly to your needs.

The website you’re currently browsing is also built with Elementor. Many of its elements were created as custom widgets to meet specific design and functionality needs. For this tutorial, we’ll use a real-world example. Creating a custom “Plans” block, similar to the one displayed on our homepage. This will help you understand how to build a widget that is both visually appealing and fully functional within Elementor.

How to create the custom widget for Elementor?

To achieve this, we’ll use the special Elementor hook elementor/widgets/register. It helps create a custom function that runs when this hook is triggered. This function will be responsible for registering our new widget so that it appears in Elementor’s editor, ready to be added to any page.

				
					add_action( 'elementor/widgets/register', 'register_custom_elementor_widget' );
				
			

Our function, register_custom_elementor_widget, receives the $widgets_manager object as a parameter. This object is where we register our new widget, making it available within Elementor’s interface. Essentially, $widgets_manager acts as the central manager for all Elementor widgets, and by adding our custom widget here, we integrate it seamlessly into the editor.

At this stage, our code has this look:

				
					function register_custom_elementor_widget( $widgets_manager ) {
        require_once( __DIR__ . '/plans/plans.php' );
        $widgets_manager->register( new \WG_Plans_Widget() );
    }
    
    add_action( 'elementor/widgets/register', 'register_custom_elementor_widget' );
				
			

We’ll move the main widget code into a separate file called plans.php. Here we’ll define the WG_Plans_Widget class. This class will handle all the functionality and rendering for our custom “Plans” widget. It helps keep code organized and modular.

Optionally, we can create our own category for Elementor widgets and add our custom widgets to it. To do this, we’ll use the elementor/elements/categories_registered hook and implement a function called add_elementor_widget_categories. This function receives the $widgets_manager object as a parameter, which we’ll use to register our custom category. It keeps our widgets organized and easy to find in the Elementor editor.

				
					function add_elementor_widget_categories( $widgets_manager ) {
        $widgets_manager->add_category(
            'webgarage',
            [
                'title' => __( 'WebGarage', 'text-domain' ),
                'icon' => 'fa fa-sharp fa-regular fa-garage',
            ]
        );
    }
    add_action( 'elementor/elements/categories_registered', 'add_elementor_widget_categories' );
				
			

Before running any of this code, we’ll perform a check to ensure Elementor is loaded by using:

				
					if ( did_action( 'elementor/loaded' ) ) {

    function register_custom_elementor_widget( $widgets_manager ) {
        require_once( __DIR__ . '/plans/plans.php' );
        $widgets_manager->register( new \WG_Plans_Widget() );
    }
    
    add_action( 'elementor/widgets/register', 'register_custom_elementor_widget' );

    function add_elementor_widget_categories( $widgets_manager ) {
        $widgets_manager->add_category(
            'webgarage',
            [
                'title' => __( 'WebGarage', 'text-domain' ),
                'icon' => 'fa fa-sharp fa-regular fa-garage',
            ]
        );
    }
    add_action( 'elementor/elements/categories_registered', 'add_elementor_widget_categories' );
}

				
			

This ensures that our custom widget code only runs after Elementor has been fully initialized, preventing errors and conflicts.

Next, inside the plans.php file, we’ll create the actual object of our widget by defining the WG_Plans_Widget class. This class will extend \Elementor\Widget_Base and will include all the necessary methods to set the widget’s name, title, icon, category, and render its content in Elementor.

				
					class WG_Plans_Widget extends \Elementor\Widget_Base {

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

    public function get_title() {
        return __( 'Plans', 'plugin-name' );
    }

    public function get_icon() {
        return 'eicon-elementor';
    }

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

    protected function _register_controls() {
        $this->start_controls_section(
            'content_section',
            [
                'label' => __( 'Content', 'plugin-name' ),
                'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
            ]
        );
    
        $this->add_control(
            'plans',
            [
                'label' => __( 'Plans', 'plugin-name' ),
                'type' => \Elementor\Controls_Manager::REPEATER,
                'fields' => [
                    [
                        'name' => 'popular',
                        'label' => __( 'Popular', 'plugin-name' ),
                        'type' => \Elementor\Controls_Manager::SWITCHER,
                        'label_on' => __( 'Yes', 'plugin-name' ),
                        'label_off' => __( 'No', 'plugin-name' ),
                        'return_value' => 'yes',
                        'default' => '',
                    ],
                    [
                        'name' => 'title',
                        'label' => __( 'Title', 'plugin-name' ),
                        'type' => \Elementor\Controls_Manager::TEXT,
                        'default' => __( 'Service Title', 'plugin-name' ),
                        'label_block' => true,
                    ],
                    [
                        'name' => 'description',
                        'label' => __( 'Description', 'plugin-name' ),
                        'type' => \Elementor\Controls_Manager::TEXTAREA,
                    ],
                    [
                        'name' => 'price',
                        'label' => __( 'Price', 'plugin-name' ),
                        'type' => \Elementor\Controls_Manager::TEXT,
                        'default' => '',
                    ],
                    [
                        'name' => 'period',
                        'label' => __( 'Period', 'plugin-name' ),
                        'type' => \Elementor\Controls_Manager::TEXT,
                        'default' => '',
                    ],
                    [
                        'name' => 'info',
                        'label' => __( 'Info', 'plugin-name' ),
                        'type' => \Elementor\Controls_Manager::REPEATER,
                        'fields' => [
                            [
                                'name' => 'item',
                                'label' => __( 'Item', 'plugin-name' ),
                                'type' => \Elementor\Controls_Manager::TEXT,
                                'default' => '',
                                'label_block' => true,
                            ],
                        ],
                        'title_field' => '{{{ item }}}',
                    ],
                    [
                        'name' => 'button_label',
                        'label' => __( 'Button Label', 'plugin-name' ),
                        'type' => \Elementor\Controls_Manager::TEXT,
                        'default' => __( 'Learn More', 'plugin-name' ),
                        'label_block' => true,
                    ],
                    [
                        'name' => 'button_link',
                        'label' => __( 'Button Link', 'plugin-name' ),
                        'type' => \Elementor\Controls_Manager::URL,
                        'placeholder' => __( 'https://example.com', 'plugin-name' ),
                    ],
                ],
                'title_field' => '{{{ title }}}',
            ]
        );        
    
        $this->end_controls_section();
    }    
    
    protected function render() {
        $settings = $this->get_settings_for_display();
        ?>
        <div class="wg-plans">
            <?php if ( ! empty( $settings['plans'] ) ) : ?>
                <?php foreach ( $settings['plans'] as $plan ) : ?>
                    <div class="wg-plan-item <?php echo esc_html( strtolower($plan['title']) ); ?>">
                        <?php if ( ! empty( $plan['popular'] ) ) : ?>
                            <div class="popular">
                                <span class="icon">
                                    <svg xmlns="http://www.w3.org/2000/svg" width="85" height="85" viewBox="0 0 85 85" fill="none">
                                        <path d="M39.4438 2.39072C41.2258 0.240196 44.5242 0.240196 46.3062 2.39072C47.7369 4.1173 50.2278 4.51182 52.1221 3.31186C54.4814 1.81727 57.6183 2.83652 58.6486 5.43246C59.4758 7.51666 61.7228 8.66159 63.8952 8.10572C66.6009 7.41336 69.2693 9.35208 69.447 12.1393C69.5896 14.3771 71.3729 16.1604 73.6107 16.303C76.3979 16.4807 78.3366 19.1491 77.6443 21.8548C77.0884 24.0272 78.2333 26.2742 80.3175 27.1014C82.9135 28.1317 83.9327 31.2686 82.4381 33.6279C81.2382 35.5222 81.6327 38.0131 83.3593 39.4438C85.5098 41.2258 85.5098 44.5242 83.3593 46.3062C81.6327 47.7369 81.2382 50.2278 82.4381 52.1221C83.9327 54.4814 82.9135 57.6183 80.3175 58.6486C78.2333 59.4758 77.0884 61.7228 77.6443 63.8952C78.3366 66.6009 76.3979 69.2693 73.6107 69.447C71.3729 69.5896 69.5896 71.3729 69.447 73.6107C69.2693 76.3979 66.6009 78.3366 63.8952 77.6443C61.7228 77.0884 59.4758 78.2333 58.6486 80.3175C57.6183 82.9135 54.4814 83.9327 52.1221 82.4381C50.2278 81.2382 47.7369 81.6327 46.3062 83.3593C44.5242 85.5098 41.2258 85.5098 39.4438 83.3593C38.0131 81.6327 35.5222 81.2382 33.6279 82.4381C31.2686 83.9327 28.1317 82.9135 27.1014 80.3175C26.2742 78.2333 24.0272 77.0884 21.8548 77.6443C19.1491 78.3366 16.4807 76.3979 16.303 73.6107C16.1604 71.3729 14.3771 69.5896 12.1393 69.447C9.35208 69.2693 7.41336 66.6009 8.10572 63.8952C8.66159 61.7228 7.51666 59.4758 5.43246 58.6486C2.83652 57.6183 1.81727 54.4814 3.31186 52.1221C4.51182 50.2278 4.1173 47.7369 2.39072 46.3062C0.240196 44.5242 0.240196 41.2258 2.39072 39.4438C4.1173 38.0131 4.51182 35.5222 3.31186 33.6279C1.81727 31.2686 2.83652 28.1317 5.43246 27.1014C7.51666 26.2742 8.66159 24.0272 8.10572 21.8548C7.41336 19.1491 9.35208 16.4807 12.1393 16.303C14.3771 16.1604 16.1604 14.3771 16.303 12.1393C16.4807 9.35208 19.1491 7.41336 21.8548 8.10572C24.0272 8.66159 26.2742 7.51666 27.1014 5.43246C28.1317 2.83652 31.2686 1.81727 33.6279 3.31186C35.5222 4.51182 38.0131 4.1173 39.4438 2.39072Z" fill="url(#paint0_linear_90_530)"/>
                                        <defs>
                                            <linearGradient id="paint0_linear_90_530" x1="42.875" y1="-1.75" x2="42.875" y2="87.5" gradientUnits="userSpaceOnUse">
                                                <stop stop-color="#0097FF"/>
                                                <stop offset="1" stop-color="#00DFFF"/>
                                            </linearGradient>
                                        </defs>
                                    </svg>
                                </span>
                                <span>Popular</span>
                            </div>
                        <?php endif; ?>

                        <?php if ( ! empty( $plan['title'] ) ) : ?>
                            <h3 class="wg-plan-title"><?php echo esc_html( $plan['title'] ); ?></h3>
                        <?php endif; ?>

                        <?php if ( ! empty( $plan['description'] ) ) : ?>
                            <div class="wg-plan-description"><?php echo $plan['description']; ?></div>
                        <?php endif; ?>

                        <?php if ( ! empty( $plan['price'] ) ) : ?>
                            <div class="wg-price">
                                <span class="price"><?php echo esc_html( $plan['price'] ); ?></span>
                                <?php if ( ! empty( $plan['period'] ) ) : ?>
                                    <span class="period"><?php echo esc_html( $plan['period'] ); ?></span>
                                <?php endif; ?>
                            </div>
                        <?php endif; ?>
                        
                        <?php
                        if ( ! empty( $plan['info'] ) || ! empty( $plan['button_label'] ) ){
                            ?>
                            <div class="line"></div>
                            <?php
                        }
                        ?>

                        <?php if ( ! empty( $plan['info'] ) ) : ?>
                            <div class="info">
                                <?php foreach ( $plan['info'] as $item ) : ?>
                                    <div class="item"><span class="icon"><svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 13 13" fill="none"><path d="M3 6.75L5.5 9.25L10.5 4.25" stroke="#00DFFF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg></span><?php echo esc_html( $item['item'] ); ?></div>
                                <?php endforeach; ?>
                            </div>
                        <?php endif; ?>

                        <?php if ( ! empty( $plan['button_label'] ) ) : ?>
                            <a class="button open-<?php echo esc_html( strtolower($plan['title']) ); ?>" href="<?php echo esc_url( isset( $plan['button_link']['url'] ) ? $plan['button_link']['url'] : '' ); ?>" 
                                target="<?php echo esc_attr( isset( $plan['button_link']['is_external'] ) && $plan['button_link']['is_external'] ? '_blank' : '_self' ); ?>" 
                                rel="<?php echo esc_attr( isset( $plan['button_link']['nofollow'] ) && $plan['button_link']['nofollow'] ? 'nofollow' : '' ); ?>">
                                <span><?php echo esc_html( $plan['button_label'] ); ?></span>
                                <span class="icon"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="19" viewBox="0 0 18 19" fill="none"><path d="M12.75 5.5L5.25 13" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M6 5.5H12.75V12.25" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
                            </a>
                        <?php endif; ?>
                    </div>
                <?php endforeach; ?>
            <?php endif; ?>
        </div>
        <?php
    }    
    
}

				
			

Next, I will describe in detail what each method of this class does.

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

The get_name() method returns a unique identifier for the widget. This ID is used internally by Elementor to register and recognize the widget. It must be in lowercase, without spaces, and unique across all widgets on the site. Think of it as the widget’s internal “slug” that Elementor relies on to distinguish it from others.

				
					public function get_title() {
        return __( 'Plans', 'plugin-name' );
    }
				
			

The get_title() method defines the display name of the widget in the Elementor editor. This is the name that users will see in the widget panel when they are building pages. Wrapping the title in the __() function makes it translatable, which is essential for multilingual websites. In this example, the title is set to “Plans.”

				
					public function get_icon() {
        return 'eicon-elementor';
    }
				
			

The get_icon() method sets the icon that appears next to the widget name in the Elementor editor. This helps users quickly identify the widget visually. Elementor provides a set of default icons prefixed with eicon-, but you can also use custom icons if needed. In our widget, the default eicon-elementor is used.

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

The get_categories() method assigns the widget to one or more Elementor categories. Categories in Elementor help organize widgets in the editor, making them easier to find. In this example, the widget is placed in a custom category called webgarage, which we have already creared before.

				
					protected function _register_controls() {
        $this->start_controls_section(
            'content_section',
            [
                'label' => __( 'Content', 'plugin-name' ),
                'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
            ]
        );
    
        $this->add_control(
            'plans',
            [
                'label' => __( 'Plans', 'plugin-name' ),
                'type' => \Elementor\Controls_Manager::REPEATER,
                'fields' => [
                    [
                        'name' => 'popular',
                        'label' => __( 'Popular', 'plugin-name' ),
                        'type' => \Elementor\Controls_Manager::SWITCHER,
                        'label_on' => __( 'Yes', 'plugin-name' ),
                        'label_off' => __( 'No', 'plugin-name' ),
                        'return_value' => 'yes',
                        'default' => '',
                    ],
                    [
                        'name' => 'title',
                        'label' => __( 'Title', 'plugin-name' ),
                        'type' => \Elementor\Controls_Manager::TEXT,
                        'default' => __( 'Service Title', 'plugin-name' ),
                        'label_block' => true,
                    ],
                    [
                        'name' => 'description',
                        'label' => __( 'Description', 'plugin-name' ),
                        'type' => \Elementor\Controls_Manager::TEXTAREA,
                    ],
                    [
                        'name' => 'price',
                        'label' => __( 'Price', 'plugin-name' ),
                        'type' => \Elementor\Controls_Manager::TEXT,
                        'default' => '',
                    ],
                    [
                        'name' => 'period',
                        'label' => __( 'Period', 'plugin-name' ),
                        'type' => \Elementor\Controls_Manager::TEXT,
                        'default' => '',
                    ],
                    [
                        'name' => 'info',
                        'label' => __( 'Info', 'plugin-name' ),
                        'type' => \Elementor\Controls_Manager::REPEATER,
                        'fields' => [
                            [
                                'name' => 'item',
                                'label' => __( 'Item', 'plugin-name' ),
                                'type' => \Elementor\Controls_Manager::TEXT,
                                'default' => '',
                                'label_block' => true,
                            ],
                        ],
                        'title_field' => '{{{ item }}}',
                    ],
                    [
                        'name' => 'button_label',
                        'label' => __( 'Button Label', 'plugin-name' ),
                        'type' => \Elementor\Controls_Manager::TEXT,
                        'default' => __( 'Learn More', 'plugin-name' ),
                        'label_block' => true,
                    ],
                    [
                        'name' => 'button_link',
                        'label' => __( 'Button Link', 'plugin-name' ),
                        'type' => \Elementor\Controls_Manager::URL,
                        'placeholder' => __( 'https://example.com', 'plugin-name' ),
                    ],
                ],
                'title_field' => '{{{ title }}}',
            ]
        );        
    
        $this->end_controls_section();
    }
				
			

The _register_controls() method helps you define all the editable settings of your widget that appear in the Elementor editor. It begins with $this->start_controls_section(), which starts a new section in the widget panel. Sections help organize controls into logical groups, labeled and optionally assigned to a tab (such as Content, Style, or Advanced). After starting a section, you add individual controls using $this->add_control() or $this->add_group_control(). Each control represents a field that the user can edit, such as text fields, textareas, URLs, switchers, or repeaters. In our Plans widget, we have a repeater control. It allows adding multiple plan items, each containing fields for title, description, price, period, features list, and button settings. Once all controls are added, $this->end_controls_section() closes the section.

Elementor provides a wide variety of control types that you can use, including: text, textarea, number, URL, switcher, select, etc. You can see the full list of available control types in the Elementor Developer Documentation. Using these controls, you can make your widget fully customizable and intuitive for users.

				
					protected function render() {
        $settings = $this->get_settings_for_display();
        ?>
        <div class="wg-plans">
            <?php if ( ! empty( $settings['plans'] ) ) : ?>
                <?php foreach ( $settings['plans'] as $plan ) : ?>
                    <div class="wg-plan-item <?php echo esc_html( strtolower($plan['title']) ); ?>">
                        <?php if ( ! empty( $plan['popular'] ) ) : ?>
                            <div class="popular">
                                <span class="icon">
                                    <svg xmlns="http://www.w3.org/2000/svg" width="85" height="85" viewBox="0 0 85 85" fill="none">
                                        <path d="M39.4438 2.39072C41.2258 0.240196 44.5242 0.240196 46.3062 2.39072C47.7369 4.1173 50.2278 4.51182 52.1221 3.31186C54.4814 1.81727 57.6183 2.83652 58.6486 5.43246C59.4758 7.51666 61.7228 8.66159 63.8952 8.10572C66.6009 7.41336 69.2693 9.35208 69.447 12.1393C69.5896 14.3771 71.3729 16.1604 73.6107 16.303C76.3979 16.4807 78.3366 19.1491 77.6443 21.8548C77.0884 24.0272 78.2333 26.2742 80.3175 27.1014C82.9135 28.1317 83.9327 31.2686 82.4381 33.6279C81.2382 35.5222 81.6327 38.0131 83.3593 39.4438C85.5098 41.2258 85.5098 44.5242 83.3593 46.3062C81.6327 47.7369 81.2382 50.2278 82.4381 52.1221C83.9327 54.4814 82.9135 57.6183 80.3175 58.6486C78.2333 59.4758 77.0884 61.7228 77.6443 63.8952C78.3366 66.6009 76.3979 69.2693 73.6107 69.447C71.3729 69.5896 69.5896 71.3729 69.447 73.6107C69.2693 76.3979 66.6009 78.3366 63.8952 77.6443C61.7228 77.0884 59.4758 78.2333 58.6486 80.3175C57.6183 82.9135 54.4814 83.9327 52.1221 82.4381C50.2278 81.2382 47.7369 81.6327 46.3062 83.3593C44.5242 85.5098 41.2258 85.5098 39.4438 83.3593C38.0131 81.6327 35.5222 81.2382 33.6279 82.4381C31.2686 83.9327 28.1317 82.9135 27.1014 80.3175C26.2742 78.2333 24.0272 77.0884 21.8548 77.6443C19.1491 78.3366 16.4807 76.3979 16.303 73.6107C16.1604 71.3729 14.3771 69.5896 12.1393 69.447C9.35208 69.2693 7.41336 66.6009 8.10572 63.8952C8.66159 61.7228 7.51666 59.4758 5.43246 58.6486C2.83652 57.6183 1.81727 54.4814 3.31186 52.1221C4.51182 50.2278 4.1173 47.7369 2.39072 46.3062C0.240196 44.5242 0.240196 41.2258 2.39072 39.4438C4.1173 38.0131 4.51182 35.5222 3.31186 33.6279C1.81727 31.2686 2.83652 28.1317 5.43246 27.1014C7.51666 26.2742 8.66159 24.0272 8.10572 21.8548C7.41336 19.1491 9.35208 16.4807 12.1393 16.303C14.3771 16.1604 16.1604 14.3771 16.303 12.1393C16.4807 9.35208 19.1491 7.41336 21.8548 8.10572C24.0272 8.66159 26.2742 7.51666 27.1014 5.43246C28.1317 2.83652 31.2686 1.81727 33.6279 3.31186C35.5222 4.51182 38.0131 4.1173 39.4438 2.39072Z" fill="url(#paint0_linear_90_530)"/>
                                        <defs>
                                            <linearGradient id="paint0_linear_90_530" x1="42.875" y1="-1.75" x2="42.875" y2="87.5" gradientUnits="userSpaceOnUse">
                                                <stop stop-color="#0097FF"/>
                                                <stop offset="1" stop-color="#00DFFF"/>
                                            </linearGradient>
                                        </defs>
                                    </svg>
                                </span>
                                <span>Popular</span>
                            </div>
                        <?php endif; ?>

                        <?php if ( ! empty( $plan['title'] ) ) : ?>
                            <h3 class="wg-plan-title"><?php echo esc_html( $plan['title'] ); ?></h3>
                        <?php endif; ?>

                        <?php if ( ! empty( $plan['description'] ) ) : ?>
                            <div class="wg-plan-description"><?php echo $plan['description']; ?></div>
                        <?php endif; ?>

                        <?php if ( ! empty( $plan['price'] ) ) : ?>
                            <div class="wg-price">
                                <span class="price"><?php echo esc_html( $plan['price'] ); ?></span>
                                <?php if ( ! empty( $plan['period'] ) ) : ?>
                                    <span class="period"><?php echo esc_html( $plan['period'] ); ?></span>
                                <?php endif; ?>
                            </div>
                        <?php endif; ?>
                        
                        <?php
                        if ( ! empty( $plan['info'] ) || ! empty( $plan['button_label'] ) ){
                            ?>
                            <div class="line"></div>
                            <?php
                        }
                        ?>

                        <?php if ( ! empty( $plan['info'] ) ) : ?>
                            <div class="info">
                                <?php foreach ( $plan['info'] as $item ) : ?>
                                    <div class="item"><span class="icon"><svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 13 13" fill="none"><path d="M3 6.75L5.5 9.25L10.5 4.25" stroke="#00DFFF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg></span><?php echo esc_html( $item['item'] ); ?></div>
                                <?php endforeach; ?>
                            </div>
                        <?php endif; ?>

                        <?php if ( ! empty( $plan['button_label'] ) ) : ?>
                            <a class="button open-<?php echo esc_html( strtolower($plan['title']) ); ?>" href="<?php echo esc_url( isset( $plan['button_link']['url'] ) ? $plan['button_link']['url'] : '' ); ?>" 
                                target="<?php echo esc_attr( isset( $plan['button_link']['is_external'] ) && $plan['button_link']['is_external'] ? '_blank' : '_self' ); ?>" 
                                rel="<?php echo esc_attr( isset( $plan['button_link']['nofollow'] ) && $plan['button_link']['nofollow'] ? 'nofollow' : '' ); ?>">
                                <span><?php echo esc_html( $plan['button_label'] ); ?></span>
                                <span class="icon"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="19" viewBox="0 0 18 19" fill="none"><path d="M12.75 5.5L5.25 13" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M6 5.5H12.75V12.25" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
                            </a>
                        <?php endif; ?>
                    </div>
                <?php endforeach; ?>
            <?php endif; ?>
        </div>
        <?php
    }
				
			

The render() method is responsible for generating the front-end output of the widget. First, we retrieve all the user-defined settings with $settings = $this->get_settings_for_display(). This gives us access to all the values entered in the widget’s fields, including titles, descriptions, prices, features, and button links. Using these settings, we then write the HTML structure of the widget, inserting dynamic content where needed. For example, we loop through each plan in a repeater field and display its title, description, price, period, feature list, and button. Conditional checks are often used to render elements only if the corresponding setting is not empty, such as showing a “Popular” badge only for plans marked as popular. This approach ensures that the widget output on the page matches exactly what the user configured in the Elementor editor.

Creating a custom Elementor widget may seem complex at first, but by breaking it down into steps—registering the widget, defining controls, and rendering the output—you can build fully customized elements tailored to your website’s needs. Using the example of the Plans widget, you’ve seen how to structure a widget class, add editable fields, handle repeaters, and output dynamic content on the front-end. Once you understand these principles, you can create virtually any widget, giving your Elementor-built site unique functionality and design that goes beyond the default options.

But if you need help with creating custom widgets, please contact us, and we will be glad to help you.

Comments

No comments yet

Share Your Thoughts

Join the conversation!

Scroll to Top

Share Your Thoughts

Thank you for your comment!

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

Pro

Thank you for reaching out! We’ll be in touch within 24 hours to discuss your project

Standard

Thank you for reaching out! We’ll be in touch within 24 hours to discuss your project

Basic

Thank you for reaching out! We’ll be in touch within 24 hours to discuss your project

Contact us

Thank you for reaching out! We’ll be in touch within 24 hours to discuss your project

Fill out the form below, and we’ll get back to you within 24 hours