---
title: "Elementor Widget Part 3: Building the Basic Widget Structure"
description: "# Introduction We’ll now create the Collection Products widget files, add its controls and register it with Elementor. # Learning Objectives By the end…"
url: "https://sajdoko.com/blog/elementor-widget-part-3-building-the-basic-widget-structure/"
language: "en"
content_signal: "ai-train=no, search=yes, ai-input=yes"
---
# Elementor Widget Part 3: Building the Basic Widget Structure

 11 November 2025·11 minutes read·[Development](https://sajdoko.com/blog/category/development/)     

## [#](#introduction) Introduction

 

We’ll now create the Collection Products widget files, add its controls and register it with Elementor.

 

## [#](#learning-objectives) 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%3A-create-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) 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%3A-create-registration-file) 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%3A-include-registration-file-in-functions.php) 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%3A-test-basic-widget) Step 4: Test Basic Widget

 

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

 

### [#](#4.1-clear-cache) 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) 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%3A-add-query-controls) 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%3A-add-helper-method-for-categories) 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%3A-add-style-controls) 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%3A-update-render-method) 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%3A-test-the-controls) 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) 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) Common Issues and Solutions

 

### [#](#issue-1%3A-widget-not-appearing-in-panel) 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%3A-controls-not-showing) 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%3A-category-dropdown-empty) 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%3A-settings-not-updating-in-preview) 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) Summary

 

The Collection Products widget now includes:

 

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

 

Check that the widget appears in Elementor and that its settings update the preview.

 

## [#](#what%E2%80%99s-next%3F) 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](https://sajdoko.com/blog/elementor-widget-part-2-understanding-elementor-widget-architecture/)  
 **Next:** [Part 4: WooCommerce Product Integration →](https://sajdoko.com/blog/elementor-widget-part-4-woocommerce-product-integration/)

  

- [Code](https://sajdoko.com/blog/category/wordpress/code/)
- [Development](https://sajdoko.com/blog/category/development/)
- [Elementor](https://sajdoko.com/blog/category/wordpress/elementor/)
- [WooCommerce](https://sajdoko.com/blog/category/wordpress/woocommerce/)
- [WordPress](https://sajdoko.com/blog/category/wordpress/)
- [Elementor Widget](https://sajdoko.com/blog/tag/elementor-widget/)

   

## Have a project in mind?

 

Write to us and we come back with a concrete estimate within one working day.

  [Contact us →](https://sajdoko.com/contact/)     

## Leave a comment [Cancel](https://sajdoko.com/blog/elementor-widget-part-3-building-the-basic-widget-structure/?format=markdown#respond)

      

## Recent Posts

- [Web and Software Development Costs in Albania: The International Buyer’s Strategic Guide](https://sajdoko.com/blog/website-costs-in-albania-international-perspective/)
- [Scaling WooCommerce Performance: Eliminating High-Traffic Checkout Bottlenecks](https://sajdoko.com/blog/scaling-woocommerce-performance-checkout-bottlenecks/)
- [The Hidden Cost of Cheap or “Free” Websites: Why €100 Web Design Costs €3,000 Later](https://sajdoko.com/blog/the-hidden-cost-of-cheap-or-free-websites/)
- [Building High-Concurrency Tour Booking Engines: Architecture Beyond Generic Plugins](https://sajdoko.com/blog/building-high-concurrency-tour-booking-engines/)
- [WordPress Malware Removal and Security Hardening: The Complete Emergency Recovery Guide](https://sajdoko.com/blog/wordpress-malware-removal-and-security-hardening/)

## Recent Comments

1. Alvaro on [Optimizing and Extending the Livewire Product Filter – Part 4](https://sajdoko.com/blog/optimizing-and-extending-the-livewire-product-filter/#comment-4)
2. [sajdoko](https://sajdoko.com) on [How to Set Up WordPress on localhost Using XAMPP: A Step-by-Step Guide](https://sajdoko.com/blog/how-to-install-wordpress-on-localhost-using-xampp-step-by-step-guide/#comment-3)
3. Roshni on [How to Set Up WordPress on localhost Using XAMPP: A Step-by-Step Guide](https://sajdoko.com/blog/how-to-install-wordpress-on-localhost-using-xampp-step-by-step-guide/#comment-2)
