Code Development Elementor WooCommerce WordPress

Elementor Widget Part 3: Building the Basic Widget Structure

# Introduction

Now that we understand how Elementor widgets work, it’s time to build the foundation of our Collection Products widget. In this part, we’ll create the widget files, implement the required methods, add basic controls, and register the widget with Elementor.

# Learning Objectives

By the end of this tutorial, you will:

  • Create the widget PHP files in the correct structure
  • Implement a complete widget class with all required methods
  • Register the widget with Elementor
  • Add basic query controls (products per page, order, category)
  • Add display controls (show/hide filters and sorting)
  • Test the widget in the Elementor editor
  • Understand how to organize widget files

# Step 1: Create Widget Files

We’ll create two PHP files to keep our code organized:

  1. collection-products-widget.php – The main widget class
  2. collection-products-register.php – Registration and helper functions

# 1.1 Create the Widget Class File

Create a new file at wp-content/themes/hello-biz-child/widgets/collection-products/collection-products-widget.php:


<?php
/**
 * Collection Products Widget for Elementor
 */

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

class Collection_Products_Widget extends \Elementor\Widget_Base {

    /**
     * Get widget name.
     *
     * @return string Widget name.
     */
    public function get_name() {
        return 'collection_products';
    }

    /**
     * Get widget title.
     *
     * @return string Widget title.
     */
    public function get_title() {
        return __( 'Collection Products', 'hello-biz-child' );
    }

    /**
     * Get widget icon.
     *
     * @return string Widget icon.
     */
    public function get_icon() {
        return 'eicon-products';
    }

    /**
     * Get widget categories.
     *
     * @return array Widget categories.
     */
    public function get_categories() {
        return [ 'woocommerce-elements' ];
    }

    /**
     * Get widget keywords.
     *
     * @return array Widget keywords.
     */
    public function get_keywords() {
        return [ 'woocommerce', 'shop', 'store', 'products', 'collection' ];
    }

    /**
     * Register widget controls.
     */
    protected function register_controls() {
        // We'll add controls in the next steps
    }

    /**
     * Render widget output on the frontend.
     */
    protected function render() {
        echo '<div class="collection-products-wrapper">';
        echo '<p>' . __( 'Collection Products Widget - Coming Soon!', 'hello-biz-child' ) . '</p>';
        echo '</div>';
    }
}

Code Explanation:

  • Security Check: if ( ! defined( 'ABSPATH' ) ) prevents direct file access
  • Class Declaration: Extends \Elementor\Widget_Base
  • get_name(): Returns unique identifier collection_products
  • get_title(): Returns display name for the widget panel
  • get_icon(): Uses Elementor’s products icon
  • get_categories(): Places widget in WooCommerce elements category
  • get_keywords(): Helps users find the widget via search
  • register_controls(): Empty for now, we’ll add controls next
  • render(): Simple placeholder output

# Step 2: Create Registration File

Create wp-content/themes/hello-biz-child/widgets/collection-products/collection-products-register.php:


<?php
/**
 * Register Elementor Collection Products Widget
 */

/**
 * Register the widget with Elementor.
 *
 * @param \Elementor\Widgets_Manager $widgets_manager Elementor widgets manager.
 * @return void
 */
function register_collection_product_widget( $widgets_manager ) {
    require_once( __DIR__ . '/collection-products-widget.php' );
    $widgets_manager->register( new \Collection_Products_Widget() );
}
add_action( 'elementor/widgets/register', 'register_collection_product_widget' );

Code Explanation:

  • Function: register_collection_product_widget() handles registration
  • require_once: Loads the widget class file
  • register(): Registers a new instance of our widget
  • Hook: elementor/widgets/register is fired when Elementor loads widgets

# Step 3: Include Registration File in functions.php

Open wp-content/themes/hello-biz-child/functions.php and add at the bottom:


// Include collection products widget registration
require_once get_stylesheet_directory() . '/widgets/collection-products/collection-products-register.php';

Complete functions.php should now look like:


<?php
/**
 * Theme functions and definitions.
 *
 * @package HelloBizChild
 */

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

define( 'HELLO_BIZ_CHILD_VERSION', '1.0.0' );

/**
 * Load child theme scripts & styles.
 *
 * @return void
 */
function hello_biz_child_scripts_styles() {

	wp_enqueue_style(
		'hello-biz-child-style',
		get_stylesheet_directory_uri() . '/style.css',
		[
			'theme',
		],
		HELLO_BIZ_CHILD_VERSION
	);
}

add_action( 'wp_enqueue_scripts', 'hello_biz_child_scripts_styles', 20 );

// Include collection products widget registration
require_once get_stylesheet_directory() . '/widgets/collection-products/collection-products-register.php';

# Step 4: Test Basic Widget

Before adding more features, let’s test that the widget loads correctly.

# 4.1 Clear Cache

  1. Clear any WordPress caching plugins
  2. Clear browser cache
  3. If using object caching, flush it

# 4.2 Test in Elementor

  1. Go to Pages → Add New
  2. Give the page a title (e.g., “Widget Test”)
  3. Click Edit with Elementor
  4. In the left panel, search for “Collection Products”
  5. Drag the widget onto the page
  6. You should see “Collection Products Widget – Coming Soon!”


Collection Products widget in the Elementor editor

Troubleshooting:

If the widget doesn’t appear:

  • Check for PHP errors (enable WP_DEBUG in wp-config.php)
  • Verify file paths are correct
  • Ensure WooCommerce is active
  • Check that all files are saved properly

# Step 5: Add Query Controls

Now let’s add controls to configure product queries. Update the register_controls() method in collection-products-widget.php:


protected function register_controls() {

    // Query Section
    $this->start_controls_section(
        'section_query',
        [
            'label' => __( 'Query', 'hello-biz-child' ),
        ]
    );

    $this->add_control(
        'products_per_page',
        [
            'label' => __( 'Products Per Page', 'hello-biz-child' ),
            'type' => \Elementor\Controls_Manager::NUMBER,
            'default' => 9,
            'min' => 3,
            'step' => 3,
            'description' => __( 'Number of products to display', 'hello-biz-child' ),
        ]
    );

    $this->add_control(
        'orderby',
        [
            'label' => __( 'Order By', 'hello-biz-child' ),
            'type' => \Elementor\Controls_Manager::SELECT,
            'default' => 'date',
            'options' => [
                'date' => __( 'Date', 'hello-biz-child' ),
                'title' => __( 'Title', 'hello-biz-child' ),
                'price' => __( 'Price', 'hello-biz-child' ),
                'popularity' => __( 'Popularity', 'hello-biz-child' ),
                'rating' => __( 'Rating', 'hello-biz-child' ),
                'rand' => __( 'Random', 'hello-biz-child' ),
                'menu_order' => __( 'Menu Order', 'hello-biz-child' ),
            ],
        ]
    );

    $this->add_control(
        'order',
        [
            'label' => __( 'Order', 'hello-biz-child' ),
            'type' => \Elementor\Controls_Manager::SELECT,
            'default' => 'DESC',
            'options' => [
                'DESC' => __( 'Descending', 'hello-biz-child' ),
                'ASC' => __( 'Ascending', 'hello-biz-child' ),
            ],
        ]
    );

    $this->add_control(
        'category',
        [
            'label' => __( 'Category', 'hello-biz-child' ),
            'type' => \Elementor\Controls_Manager::SELECT2,
            'multiple' => true,
            'options' => $this->get_product_categories(),
            'label_block' => true,
            'description' => __( 'Select categories to filter products', 'hello-biz-child' ),
        ]
    );

    $this->add_control(
        'show_filters',
        [
            'label' => __( 'Show Filters', 'hello-biz-child' ),
            'type' => \Elementor\Controls_Manager::SWITCHER,
            'label_on' => __( 'Yes', 'hello-biz-child' ),
            'label_off' => __( 'No', 'hello-biz-child' ),
            'return_value' => 'yes',
            'default' => 'yes',
        ]
    );

    $this->add_control(
        'show_sorting',
        [
            'label' => __( 'Show Sorting', 'hello-biz-child' ),
            'type' => \Elementor\Controls_Manager::SWITCHER,
            'label_on' => __( 'Yes', 'hello-biz-child' ),
            'label_off' => __( 'No', 'hello-biz-child' ),
            'return_value' => 'yes',
            'default' => 'yes',
        ]
    );

    $this->end_controls_section();
}

Code Explanation:

  • Products Per Page: Number control with default of 9, minimum 3, step of 3
  • Order By: Dropdown with common sorting options
  • Order: ASC or DESC
  • Category: Multi-select dropdown of product categories
  • Show Filters: Toggle to show/hide filter panel
  • Show Sorting: Toggle to show/hide sorting dropdown

# Step 6: Add Helper Method for Categories

Add this helper method to the widget class (before or after register_controls()):


/**
 * Get product categories for control options.
 *
 * @return array Categories array.
 */
protected function get_product_categories() {
    $categories = get_terms( 'product_cat', [
        'hide_empty' => false,
    ] );

    $options = [];
    
    if ( ! empty( $categories ) && ! is_wp_error( $categories ) ) {
        foreach ( $categories as $category ) {
            $options[ $category->term_id ] = $category->name;
        }
    }

    return $options;
}

Code Explanation:

  • get_terms(): Retrieves WooCommerce product categories
  • hide_empty => false: Include categories with no products
  • Returns: Array formatted for SELECT2 control

# Step 7: Add Style Controls

After the query section, add a style section:


// Style Section
$this->start_controls_section(
    'section_style',
    [
        'label' => __( 'Style', 'hello-biz-child' ),
        'tab' => \Elementor\Controls_Manager::TAB_STYLE,
    ]
);

$this->add_control(
    'gap',
    [
        'label' => __( 'Gap', 'hello-biz-child' ),
        'type' => \Elementor\Controls_Manager::SLIDER,
        'size_units' => [ 'px' ],
        'range' => [
            'px' => [
                'min' => 0,
                'max' => 50,
                'step' => 1,
            ],
        ],
        'default' => [
            'unit' => 'px',
            'size' => 10,
        ],
        'selectors' => [
            '{{WRAPPER}} .collection-products-grid' => 'gap: {{SIZE}}{{UNIT}};',
        ],
    ]
);

$this->end_controls_section();

Code Explanation:

  • TAB_STYLE: Places this section in the Style tab
  • SLIDER: Creates a range slider control
  • selectors: Automatically applies CSS when slider changes
  • {{WRAPPER}}: Replaced with unique widget selector
  • {{SIZE}}{{UNIT}}: Replaced with selected value (e.g., “10px”)

# Step 8: Update Render Method

Update the render() method to display settings:


protected function render() {
    $settings = $this->get_settings_for_display();
    ?>
    <div class="collection-products-wrapper">
        <h3>Collection Products Widget</h3>
        <p><strong>Settings:</strong></p>
        <ul>
            <li>Products per page: <?php echo esc_html( $settings['products_per_page'] ); ?></li>
            <li>Order by: <?php echo esc_html( $settings['orderby'] ); ?></li>
            <li>Order: <?php echo esc_html( $settings['order'] ); ?></li>
            <li>Show filters: <?php echo esc_html( $settings['show_filters'] ); ?></li>
            <li>Show sorting: <?php echo esc_html( $settings['show_sorting'] ); ?></li>
        </ul>
    </div>
    <?php
}

This temporary render method helps us verify that settings are working correctly.

# Step 9: Test the Controls

  1. Refresh your Elementor editor page (or edit a new page)
  2. Add/re-add the Collection Products widget
  3. In the left panel, you should see:
    • Content tab with Query section
    • Style tab with Style section
  4. Try changing values:
    • Set “Products Per Page” to 12
    • Change “Order By” to “Price”
    • Toggle “Show Filters” off and on
  5. Verify the values appear in the preview

# Complete Widget Code So Far

Here’s what your collection-products-widget.php should look like at this point:


<?php
/**
 * Collection Products Widget for Elementor
 */

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

class Collection_Products_Widget extends \Elementor\Widget_Base {

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

    public function get_title() {
        return __( 'Collection Products', 'hello-biz-child' );
    }

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

    public function get_categories() {
        return [ 'woocommerce-elements' ];
    }

    public function get_keywords() {
        return [ 'woocommerce', 'shop', 'store', 'products', 'collection' ];
    }

    protected function register_controls() {

        $this->start_controls_section(
            'section_query',
            [
                'label' => __( 'Query', 'hello-biz-child' ),
            ]
        );

        $this->add_control(
            'products_per_page',
            [
                'label' => __( 'Products Per Page', 'hello-biz-child' ),
                'type' => \Elementor\Controls_Manager::NUMBER,
                'default' => 9,
                'min' => 3,
                'step' => 3,
            ]
        );

        $this->add_control(
            'orderby',
            [
                'label' => __( 'Order By', 'hello-biz-child' ),
                'type' => \Elementor\Controls_Manager::SELECT,
                'default' => 'date',
                'options' => [
                    'date' => __( 'Date', 'hello-biz-child' ),
                    'title' => __( 'Title', 'hello-biz-child' ),
                    'price' => __( 'Price', 'hello-biz-child' ),
                    'popularity' => __( 'Popularity', 'hello-biz-child' ),
                    'rating' => __( 'Rating', 'hello-biz-child' ),
                    'rand' => __( 'Random', 'hello-biz-child' ),
                    'menu_order' => __( 'Menu Order', 'hello-biz-child' ),
                ],
            ]
        );

        $this->add_control(
            'order',
            [
                'label' => __( 'Order', 'hello-biz-child' ),
                'type' => \Elementor\Controls_Manager::SELECT,
                'default' => 'DESC',
                'options' => [
                    'DESC' => __( 'Descending', 'hello-biz-child' ),
                    'ASC' => __( 'Ascending', 'hello-biz-child' ),
                ],
            ]
        );

        $this->add_control(
            'category',
            [
                'label' => __( 'Category', 'hello-biz-child' ),
                'type' => \Elementor\Controls_Manager::SELECT2,
                'multiple' => true,
                'options' => $this->get_product_categories(),
                'label_block' => true,
            ]
        );

        $this->add_control(
            'show_filters',
            [
                'label' => __( 'Show Filters', 'hello-biz-child' ),
                'type' => \Elementor\Controls_Manager::SWITCHER,
                'label_on' => __( 'Yes', 'hello-biz-child' ),
                'label_off' => __( 'No', 'hello-biz-child' ),
                'return_value' => 'yes',
                'default' => 'yes',
            ]
        );

        $this->add_control(
            'show_sorting',
            [
                'label' => __( 'Show Sorting', 'hello-biz-child' ),
                'type' => \Elementor\Controls_Manager::SWITCHER,
                'label_on' => __( 'Yes', 'hello-biz-child' ),
                'label_off' => __( 'No', 'hello-biz-child' ),
                'return_value' => 'yes',
                'default' => 'yes',
            ]
        );

        $this->end_controls_section();

        // Style Section
        $this->start_controls_section(
            'section_style',
            [
                'label' => __( 'Style', 'hello-biz-child' ),
                'tab' => \Elementor\Controls_Manager::TAB_STYLE,
            ]
        );

        $this->add_control(
            'gap',
            [
                'label' => __( 'Gap', 'hello-biz-child' ),
                'type' => \Elementor\Controls_Manager::SLIDER,
                'size_units' => [ 'px' ],
                'range' => [
                    'px' => [
                        'min' => 0,
                        'max' => 50,
                        'step' => 1,
                    ],
                ],
                'default' => [
                    'unit' => 'px',
                    'size' => 10,
                ],
                'selectors' => [
                    '{{WRAPPER}} .collection-products-grid' => 'gap: {{SIZE}}{{UNIT}};',
                ],
            ]
        );

        $this->end_controls_section();
    }

    protected function get_product_categories() {
        $categories = get_terms( 'product_cat', [
            'hide_empty' => false,
        ] );

        $options = [];
        foreach ( $categories as $category ) {
            $options[ $category->term_id ] = $category->name;
        }

        return $options;
    }

    protected function render() {
        $settings = $this->get_settings_for_display();
        ?>
        <div class="collection-products-wrapper">
            <h3>Collection Products Widget</h3>
            <p>Settings:</p>
            <ul>
                <li>Products per page: <?php echo esc_html( $settings['products_per_page'] ); ?></li>
                <li>Order by: <?php echo esc_html( $settings['orderby'] ); ?></li>
                <li>Order: <?php echo esc_html( $settings['order'] ); ?></li>
                <li>Show filters: <?php echo esc_html( $settings['show_filters'] ); ?></li>
                <li>Show sorting: <?php echo esc_html( $settings['show_sorting'] ); ?></li>
            </ul>
        </div>
        <?php
    }
}

# Common Issues and Solutions

# Issue 1: Widget Not Appearing in Panel

Solutions:

  • Verify get_categories() returns a valid category
  • Check that WooCommerce is active (required for ‘woocommerce-elements’ category)
  • Clear Elementor cache: Elementor → Tools → Regenerate CSS
  • Check PHP error logs

# Issue 2: Controls Not Showing

Solutions:

  • Verify register_controls() is called correctly
  • Check for PHP syntax errors
  • Ensure start_controls_section() is matched with end_controls_section()
  • Verify control IDs are unique

# Issue 3: Category Dropdown Empty

Solutions:

  • Create some product categories in WooCommerce
  • Check get_product_categories() method
  • Verify WooCommerce is installed and active

# Issue 4: Settings Not Updating in Preview

Solutions:

  • Refresh the Elementor editor
  • Check browser console for JavaScript errors
  • Verify get_settings_for_display() is used in render()

# Summary

Congratulations! You’ve built the foundation of the Collection Products widget:

  • Created widget class file
  • Created registration file
  • Registered widget with Elementor
  • Added query controls
  • Added style controls
  • Tested widget in Elementor editor

Your widget now appears in the Elementor panel and has a full settings interface!

# What’s Next?

In Part 4: WooCommerce Product Integration, we’ll:

  • Query WooCommerce products using WP_Query
  • Access and display product data
  • Work with product categories and taxonomies
  • Display product prices and images
  • Handle product variations
  • Create the initial product grid layout

Previous: ← Part 2: Understanding Elementor Widget Architecture
Next: Part 4: WooCommerce Product Integration →

Chat on WhatsApp Chat on WhatsApp