Back to Articles
25 min read

Engineering Custom Elementor Widgets: The Controls Stack, Group Classes, and Data Repeaters

Unlock the full potential of Elementor's extensibility API. This technical reference walks through the complete lifecycle of custom widget development—from class instantiation and registration to the granular implementation of the Controls Stack. We cover syntax for basic and advanced inputs, leverage Group Controls for standardized styling (Typography, Borders), and implement the Repeater class for iterable data structures, ensuring your custom components function natively within the editor.

Widget Development Basics

Widget File Structure

An Elementor widget follows a specific file organization pattern within your plugin or theme. The widget class file is typically placed in a dedicated widgets folder, and must be properly included and registered with Elementor's widget manager during the elementor/widgets/widgets_registered action hook.

my-elementor-addon/ ├── my-elementor-addon.php # Main plugin file ├── widgets/ │ ├── my-widget.php # Widget class file │ └── another-widget.php ├── assets/ │ ├── css/ │ └── js/ └── includes/ └── plugin.php # Plugin loader

Widget Class Creation

A widget class is a PHP class that encapsulates all widget functionality including controls definition, rendering logic, and editor preview. The class must extend \Elementor\Widget_Base and implement required abstract methods to define the widget's behavior and appearance in both editor and frontend.

<?php namespace MyAddon\Widgets; class My_Custom_Widget extends \Elementor\Widget_Base { // Widget methods go here public function get_name() { return 'my-widget'; } public function get_title() { return 'My Widget'; } protected function render() { echo '<div>Hello World</div>'; } }

Extending Widget_Base

Widget_Base is the abstract base class provided by Elementor that all custom widgets must extend. It provides the core infrastructure for controls registration, rendering, content template generation, and integration with Elementor's editor—you override its methods to customize your widget's behavior.

<?php use Elementor\Widget_Base; class My_Widget extends Widget_Base { // Must implement: get_name(), get_title() // Should implement: get_icon(), get_categories() // Core methods: register_controls(), render(), content_template() }

Widget Registration

Widget registration tells Elementor about your custom widget so it appears in the editor panel. This is done by hooking into elementor/widgets/register (Elementor 3.5+) or elementor/widgets/widgets_registered (legacy) and calling the register() method on the widgets manager with your widget instance.

<?php add_action('elementor/widgets/register', function($widgets_manager) { require_once(__DIR__ . '/widgets/my-widget.php'); $widgets_manager->register(new \My_Custom_Widget()); });

get_name() Method

Returns the unique programmatic identifier (slug) for your widget, used internally by Elementor for widget identification, data storage, and CSS class generation. This should be lowercase with hyphens, without spaces, and must be unique across all registered widgets.

public function get_name() { return 'my-custom-heading'; // Unique slug identifier } // Used in: CSS classes, data attributes, internal references // Output: elementor-widget-my-custom-heading

get_title() Method

Returns the human-readable display name shown in Elementor's widget panel and editor interface. This is what users see when browsing available widgets, so it should be descriptive, properly capitalized, and optionally translatable using WordPress i18n functions.

public function get_title() { return esc_html__('My Custom Heading', 'my-text-domain'); }
┌─────────────────────────┐ │ ELEMENTS PANEL │ ├─────────────────────────┤ │ 🔤 My Custom Heading │ ← Title shown here │ 📝 Text Editor │ │ 🖼️ Image │ └─────────────────────────┘

get_icon() Method

Returns the CSS class for the icon displayed next to your widget in the Elementor panel. You can use Elementor's eicon-* icons, WordPress Dashicons (dashicons-*), Font Awesome (fa fa-*), or custom icon classes from your own icon font.

public function get_icon() { return 'eicon-heading'; // Elementor icon // return 'dashicons-admin-post'; // Dashicon // return 'fa fa-star'; // Font Awesome } // Common eicons: eicon-heading, eicon-text, eicon-image, // eicon-button, eicon-gallery-grid

Elementor default icons (eicon‑)

Icon nameDescription
eicon-editor-codeeicon-code – generic code icon
eicon-editor-boldeicon-bold – bold text
eicon-editor-italiceicon-italic – italic text
eicon-editor-underlineeicon-underline – underline
eicon-editor-alignlefteicon-align-left – left align
eicon-editor-aligncentereicon-align-center – center align
eicon-editor-alignrighteicon-align-right – right align
eicon-editor-alignjustifyeicon-align-justify – justify
eicon-editor-list-uleicon-list-ul – unordered list
eicon-editor-list-oleicon-list-ol – ordered list
eicon-editor-linkeicon-link – hyperlink
eicon-editor-unlinkeicon-unlink – remove link
eicon-editor-tableeicon-table – table
eicon-editor-insertimageeicon-image – image insert
eicon-editor-videoeicon-video – video insert
eicon-editor-insertfileeicon-file – file insert
eicon-editor-insertmediaeicon-media – media library
eicon-editor-quoteeicon-quote – blockquote
eicon-editor-codeeicon-code – code block
eicon-editor-paragrapheicon-paragraph – paragraph
eicon-editor-headingeicon-heading – heading
eicon-editor-hreicon-hr – horizontal rule
eicon-editor-strikethrougheicon-strikethrough – strikethrough
eicon-editor-subscripteicon-subscript – subscript
eicon-editor-superscripteicon-superscript – superscript
eicon-editor-undoeicon-undo – undo
eicon-editor-redoeicon-redo – redo
eicon-editor-cuteicon-cut – cut
eicon-editor-copyeicon-copy – copy
eicon-editor-pasteeicon-paste – paste
eicon-editor-selectalleicon-select-all – select all
eicon-editor-clearformateicon-clear-format – clear formatting
eicon-editor-spellcheckeicon-spellcheck – spell check
eicon-editor-emojieicon-emoji – emoji
eicon-editor-helpeicon-help – help/info
eicon-editor-settingseicon-settings – settings
eicon-editor-searcheicon-search – search
eicon-editor-closeeicon-close – close
eicon-editor-checkeicon-check – check/confirm
eicon-editor-pluseicon-plus – add
eicon-editor-minuseicon-minus – remove
eicon-editor-arrow-upeicon-arrow-up – up arrow
eicon-editor-arrow-downeicon-arrow-down – down arrow
eicon-editor-arrow-lefteicon-arrow-left – left arrow
eicon-editor-arrow-righteicon-arrow-right – right arrow
eicon-editor-stareicon-star – star/favorite
eicon-editor-hearteicon-heart – heart/like
eicon-editor-belleicon-bell – notifications
eicon-editor-calendareicon-calendar – calendar
eicon-editor-clockeicon-clock – clock/time
eicon-editor-usereicon-user – user/profile
eicon-editor-userseicon-users – multiple users
eicon-editor-lockeicon-lock – lock/security
eicon-editor-unlockeicon-unlock – unlock
eicon-editor-keyeicon-key – key
eicon-editor-wrencheicon-wrench – tools
eicon-editor-cogeicon-cog – settings gear
eicon-editor-trasheicon-trash – delete
eicon-editor-downloadeicon-download – download
eicon-editor-uploadeicon-upload – upload
eicon-editor-shareeicon-share – share
eicon-editor-link-externaleicon-external-link – external link
eicon-editor-globeeicon-globe – globe/internet
eicon-editor-map-pineicon-map-pin – location pin
eicon-editor-phoneeicon-phone – phone
eicon-editor-emaileicon-email – email
eicon-editor-chateicon-chat – chat/message
eicon-editor-commenteicon-comment – comment
eicon-editor-quote-lefteicon-quote-left – opening quote
eicon-editor-quote-righteicon-quote-right – closing quote
eicon-editor-quote-alteicon-quote-alt – alternate quote
eicon-editor-quote-alt2eicon-quote-alt2 – second alternate
eicon-editor-quote-alt3eicon-quote-alt3 – third alternate
eicon-editor-quote-alt4eicon-quote-alt4 – fourth alternate
eicon-editor-quote-alt5eicon-quote-alt5 – fifth alternate
eicon-editor-quote-alt6eicon-quote-alt6 – sixth alternate
eicon-editor-quote-alt7eicon-quote-alt7 – seventh alternate
eicon-editor-quote-alt8eicon-quote-alt8 – eighth alternate
eicon-editor-quote-alt9eicon-quote-alt9 – ninth alternate
eicon-editor-quote-alt10eicon-quote-alt10 – tenth alternate

get_categories() Method

Returns an array of category slugs where your widget should appear in the Elementor panel. Built-in categories include basic, general, pro-elements, theme-elements, and woocommerce-elements, you can also register custom categories for grouping your widgets.

public function get_categories() { return ['basic', 'general']; // Appears in both categories } // Registering custom category: add_action('elementor/elements/categories_registered', function($elements_manager) { $elements_manager->add_category('my-category', [ 'title' => 'My Widgets', 'icon' => 'fa fa-plug', ]); });

get_keywords() Method

Returns an array of search keywords that help users find your widget when typing in the Elementor panel search box. Include synonyms, related terms, and common misspellings to improve widget discoverability beyond just the widget title.

public function get_keywords() { return ['heading', 'title', 'text', 'headline', 'h1', 'header']; }
Search: "headline" ┌──────────────────────┐ │ 🔍 headline │ ├──────────────────────┤ │ 🔤 My Custom Heading │ ← Matched via keywords │ 📝 Heading │ └──────────────────────┘

Widget Controls

Controls Introduction

Controls are the UI elements in Elementor's editor panel that allow users to customize widget settings—they range from simple text inputs to complex color pickers and media uploaders. Each control type stores a value that you retrieve in the render() method using $this->get_settings_for_display() to generate dynamic output.

┌─────────────────────────────────────────┐ │ WIDGET PANEL │ ├─────────────────────────────────────────┤ │ Content Style Advanced │ │ ───────────────────────────────────── │ │ Section: Text Settings │ │ ┌───────────────────────────────────┐ │ │ │ Title: [________________] TEXT │ │ │ │ Color: [■ #333333 ] COLOR │ │ │ │ Size: [ 16 ] px NUMBER │ │ │ └───────────────────────────────────┘ │ └─────────────────────────────────────────┘

start_controls_section()

Opens a new collapsible section in the editor panel to group related controls together. You must specify a unique section ID, label, and the tab where it appears (content, style, or advanced). Every start_controls_section() must have a corresponding end_controls_section().

$this->start_controls_section( 'content_section', // Unique section ID [ 'label' => esc_html__('Content', 'my-domain'), 'tab' => \Elementor\Controls_Manager::TAB_CONTENT, // Other tabs: TAB_STYLE, TAB_ADVANCED ] ); // Add controls here... $this->end_controls_section();

end_controls_section()

Closes a previously opened controls section—it's mandatory and must be called after start_controls_section() once you've finished adding all controls to that section. Forgetting this call will cause PHP errors and break the widget editor interface.

$this->start_controls_section('section_content', [...]); $this->add_control('title', [...]); $this->add_control('description', [...]); $this->end_controls_section(); // ← Required closure $this->start_controls_section('section_style', [...]); // New section controls... $this->end_controls_section(); // ← Each section needs its own close

add_control()

The primary method for adding a single control element to the current section. It takes a unique control ID, an array of arguments defining the control type, label, default value, and other options—the stored value is accessed in render via $settings['control_id'].

$this->add_control( 'widget_title', // Control ID (used to get value) [ 'label' => esc_html__('Title', 'my-domain'), 'type' => \Elementor\Controls_Manager::TEXT, 'default' => esc_html__('Default Title', 'my-domain'), 'placeholder' => esc_html__('Enter title', 'my-domain'), ] ); // In render(): $settings = $this->get_settings_for_display(); echo '<h2>' . esc_html($settings['widget_title']) . '</h2>';

Control Types Overview

Elementor provides numerous control types accessed via Controls_Manager constants, categorized as: Data controls ( TEXT, NUMBER, URL), UI controls (SELECT, SWITCHER, CHOOSE), Multi-value controls (DIMENSIONS, SLIDER), Unit controls (TYPOGRAPHY, BOX_SHADOW), and Group controls for complex styling options.

┌─────────────────────────────────────────────────────────┐ │ CONTROL TYPES │ ├───────────────┬───────────────┬─────────────────────────┤ │ Data │ UI │ Complex │ ├───────────────┼───────────────┼─────────────────────────┤ │ TEXT │ SELECT │ SLIDER │ │ TEXTAREA │ SELECT2 │ DIMENSIONS │ │ NUMBER │ SWITCHER │ MEDIA │ │ WYSIWYG │ CHOOSE │ GALLERY │ │ URL │ COLOR │ REPEATER │ │ CODE │ DATE_TIME │ ICONS │ │ HIDDEN │ BUTTON │ Group Controls │ └───────────────┴───────────────┴─────────────────────────┘

Elementor Control Types (Controls_Manager)

Control constantCategoryTypical use / brief explanation
TEXTDataSingle‑line text input for strings (e.g., CSS class, custom ID).
TEXTAREADataMulti‑line text area, often for custom HTML, CSS or long descriptions.
NUMBERDataNumeric input with optional min/max/step; used for sizes, counts, etc.
URLDataInput that validates a URL; automatically adds http:// if missing.
SELECTUIDropdown list of predefined options; good for choosing a style preset.
SWITCHERUIOn/off toggle (boolean); renders as a modern switch UI.
CHOOSEUIVisual choice set (icons or images) for options like alignment, position.
COLORUIColor picker (hex, rgba, gradient); often paired with opacity control.
MEDIAUIOpens the WordPress media library to select images, videos, or files.
ICONUIIcon selector (Font Awesome, Elementor icons, custom SVG).
SLIDERMulti‑valueSlider bar for numeric ranges; can show value tooltip while dragging.
DIMENSIONSMulti‑valueFour‑field input (top, right, bottom, left) for margins, paddings, borders.
BORDERMulti‑valueCombines width, style, and color for border definitions.
BOX_SHADOWUnitControls horizontal offset, vertical offset, blur, spread, and color of a shadow.
TYPOGRAPHYUnitGroup control covering font family, size, weight, line‑height, letter‑spacing, text‑transform, and style.
HEADINGUINon‑interactive label used to separate sections inside the panel.
RAW_HTMLUIDisplays raw HTML (help text, links, documentation) inside the control panel.
CODEUICode editor with syntax highlighting (HTML, CSS, JS) for custom snippets.
REPEATERComplexAllows repeating a set of sub‑controls (e.g., list items, slides).
GROUP_CONTROLGroupBase class for complex styling groups (e.g., Group_Control_Typography, Group_Control_Background).
BACKGROUND_GROUPGroupHandles background types (classic, gradient, video) and related options.
IMAGE_DIMENSIONSMulti‑valueWidth × height selector for image size, often with a “custom” option.
DATE_TIMEDataDate and/or time picker, stored as a string in ISO format.
HIDDENDataInvisible field used to store internal values; not shown to the user.
POPUPUIOpens a modal popup to configure nested settings (used in advanced widgets).
TABSUIOrganises controls into tabbed sections for better UX.
SECTIONUIVisual separator that groups related controls under a collapsible heading.

Control Arguments

Each control accepts an array of arguments that configure its behavior: label (display name), type (control type constant), default (initial value), placeholder, description, separator, condition, selectors, and type-specific options like options for SELECT or min/max for NUMBER.

$this->add_control( 'example_control', [ 'label' => esc_html__('Label', 'domain'), // Display text 'type' => Controls_Manager::TEXT, // Control type 'default' => 'Default value', // Initial value 'placeholder' => 'Placeholder text', // Hint text 'description' => 'Help text below control', // Help text 'label_block' => true, // Full-width label 'separator' => 'before', // Visual separator 'classes' => 'custom-css-class', // Custom class ] );

Conditions

Conditions allow you to show or hide controls based on other control values, creating dynamic and contextual editor interfaces. You can use simple equality checks, multiple conditions (AND logic), or the conditions key for complex OR/AND combinations.

$this->add_control('show_title', [ 'type' => Controls_Manager::SWITCHER, 'default' => 'yes', ]); $this->add_control('title_text', [ 'type' => Controls_Manager::TEXT, 'condition' => [ 'show_title' => 'yes', // Simple condition ], ]); $this->add_control('title_color', [ 'type' => Controls_Manager::COLOR, 'condition' => [ 'show_title' => 'yes', 'title_text!' => '', // NOT empty (! operator) ], ]);

Selectors

Selectors enable controls to directly output CSS properties without PHP processing, connecting control values to CSS rules using placeholders like {{VALUE}}, {{UNIT}}, and {{SIZE}}. This is the recommended approach for style controls as it leverages Elementor's CSS caching system.

$this->add_control( 'title_color', [ 'label' => 'Color', 'type' => Controls_Manager::COLOR, 'selectors' => [ '{{WRAPPER}} .widget-title' => 'color: {{VALUE}};', '{{WRAPPER}} .widget-title a' => 'color: {{VALUE}};', ], ] ); // {{WRAPPER}} = .elementor-element-{element-id} // {{VALUE}} = The control's stored value

Default Values

Default values define the initial state of a control when a widget is first added to the page. The format varies by control type—simple values for basic controls, arrays for complex controls like DIMENSIONS or SLIDER, and proper localization should be applied for translatable text defaults.

// Simple default 'default' => 'Hello World', // Slider default with unit 'default' => [ 'size' => 50, 'unit' => 'px', ], // Dimensions default 'default' => [ 'top' => '10', 'right' => '20', 'bottom' => '10', 'left' => '20', 'unit' => 'px', 'isLinked' => false, ], // URL default 'default' => [ 'url' => 'https://example.com', 'is_external' => true, ],

Basic Controls

TEXT Control

A single-line text input field for short text content like headings, labels, or custom CSS classes. Supports placeholder, label_block for full-width display, dynamic tags for Elementor Pro, and input_type attribute for HTML5 input variations.

$this->add_control( 'widget_title', [ 'label' => esc_html__('Title', 'my-domain'), 'type' => \Elementor\Controls_Manager::TEXT, 'default' => esc_html__('Default Title', 'my-domain'), 'placeholder' => esc_html__('Enter your title', 'my-domain'), 'label_block' => true, 'dynamic' => ['active' => true], // Enable dynamic tags 'ai' => ['active' => false], // Disable AI (Elementor 3.17+) ] );

TEXTAREA Control

A multi-line text input for longer content like descriptions, paragraphs, or custom HTML/CSS snippets. Supports rows argument to control height, and unlike WYSIWYG, outputs raw text requiring manual HTML escaping in render.

$this->add_control( 'description', [ 'label' => esc_html__('Description', 'my-domain'), 'type' => \Elementor\Controls_Manager::TEXTAREA, 'default' => esc_html__('Default description text...', 'my-domain'), 'placeholder' => esc_html__('Enter description', 'my-domain'), 'rows' => 5, // Number of visible rows ] ); // In render(): echo '<p>' . esc_html($settings['description']) . '</p>'; // Or with line breaks preserved: echo '<p>' . nl2br(esc_html($settings['description'])) . '</p>';

NUMBER Control

A numeric input field with optional min, max, and step constraints for precise numerical values. Ideal for quantities, z-index, animation delays, or any integer/decimal value that doesn't require unit selection (use SLIDER for values with units).

$this->add_control( 'items_count', [ 'label' => esc_html__('Number of Items', 'my-domain'), 'type' => \Elementor\Controls_Manager::NUMBER, 'min' => 1, 'max' => 100, 'step' => 1, 'default' => 5, ] ); // For decimal values: $this->add_control('opacity', [ 'type' => Controls_Manager::NUMBER, 'min' => 0, 'max' => 1, 'step' => 0.1, 'default' => 1, ]);

URL Control

A specialized input for URLs with additional fields for "open in new window" and "nofollow" options. Returns an array with url, is_external, nofollow, and custom_attributes keys that should be properly rendered using $this->add_link_attributes() method.

$this->add_control( 'website_link', [ 'label' => esc_html__('Link', 'my-domain'), 'type' => \Elementor\Controls_Manager::URL, 'placeholder' => esc_html__('https://your-link.com', 'my-domain'), 'default' => [ 'url' => '', 'is_external' => true, 'nofollow' => true, ], 'options' => ['url', 'is_external', 'nofollow'], // Visible options ] ); // In render(): $this->add_link_attributes('button_link', $settings['website_link']); echo '<a ' . $this->get_render_attribute_string('button_link') . '>Click</a>';

WYSIWYG Control

A full WordPress TinyMCE rich text editor for complex formatted content with paragraphs, lists, links, and basic formatting. The output is already HTML-safe and should be echoed directly; use wp_kses_post() if you need additional sanitization.

$this->add_control( 'content', [ 'label' => esc_html__('Content', 'my-domain'), 'type' => \Elementor\Controls_Manager::WYSIWYG, 'default' => '<p>' . esc_html__('Default content...', 'my-domain') . '</p>', ] ); // In render() - already contains HTML, safe to output: echo '<div class="content-wrapper">' . $settings['content'] . '</div>'; // Or with extra safety: echo wp_kses_post($settings['content']);

CODE Control

A syntax-highlighted code editor for HTML, CSS, JavaScript, or other code input using CodeMirror. Specify the language argument for proper syntax highlighting; the value should be properly escaped or sanitized based on the expected code type.

$this->add_control( 'custom_html', [ 'label' => esc_html__('Custom HTML', 'my-domain'), 'type' => \Elementor\Controls_Manager::CODE, 'language' => 'html', // html, css, javascript, json, etc. 'rows' => 10, 'default' => '<div class="custom">Content</div>', ] ); $this->add_control( 'custom_css', [ 'label' => esc_html__('Custom CSS', 'my-domain'), 'type' => Controls_Manager::CODE, 'language' => 'css', 'default' => '.my-class { color: red; }', ] );

HIDDEN Control

A non-visible control that stores values programmatically without displaying in the editor panel. Useful for storing internal data, version numbers, or computed values that shouldn't be user-editable but need to persist in widget settings.

$this->add_control( 'widget_version', [ 'label' => '', 'type' => \Elementor\Controls_Manager::HIDDEN, 'default' => '1.0.0', ] ); $this->add_control( 'internal_id', [ 'type' => Controls_Manager::HIDDEN, 'default' => uniqid('widget_'), ] ); // Value accessible in render(): $settings['widget_version']

SWITCHER Control

A toggle switch for boolean on/off options that stores yes or empty string (not true/false). Commonly used for showing/hiding elements, enabling features, or toggling between two states with label_on, label_off, and return_value customization.

$this->add_control( 'show_title', [ 'label' => esc_html__('Show Title', 'my-domain'), 'type' => \Elementor\Controls_Manager::SWITCHER, 'label_on' => esc_html__('Yes', 'my-domain'), 'label_off' => esc_html__('No', 'my-domain'), 'return_value' => 'yes', // Value when ON 'default' => 'yes', // '' (empty) = OFF ] ); // In render(): if ('yes' === $settings['show_title']) { echo '<h2>' . esc_html($settings['title']) . '</h2>'; }

SELECT Control

A dropdown select menu for choosing one option from a predefined list. Options are defined as an associative array where keys are stored values and values are display labels; supports multiple for multi-select but SELECT2 is preferred for that use case.

$this->add_control( 'heading_tag', [ 'label' => esc_html__('HTML Tag', 'my-domain'), 'type' => \Elementor\Controls_Manager::SELECT, 'default' => 'h2', 'options' => [ 'h1' => 'H1', 'h2' => 'H2', 'h3' => 'H3', 'h4' => 'H4', 'p' => 'Paragraph', 'div' => 'Div', 'span' => 'Span', ], ] ); // In render(): $tag = $settings['heading_tag']; echo "<{$tag}>" . esc_html($settings['title']) . "</{$tag}>";

SELECT2 Control

An enhanced dropdown using the Select2 library with search, tagging, and multi-select capabilities. Use multiple => true for selecting multiple values (returns array); ideal for selecting posts, terms, or any large option list requiring search functionality.

$this->add_control( 'selected_categories', [ 'label' => esc_html__('Categories', 'my-domain'), 'type' => \Elementor\Controls_Manager::SELECT2, 'multiple' => true, 'options' => $this->get_categories_options(), // Your method 'default' => [], 'label_block' => true, ] ); // Returns array when multiple: ['cat1', 'cat2', 'cat3'] // Returns string when single: 'cat1' private function get_categories_options() { $categories = get_categories(); $options = []; foreach ($categories as $cat) { $options[$cat->term_id] = $cat->name; } return $options; }

CHOOSE Control

A visual icon-based toggle group for selecting from mutually exclusive options like alignment, layout direction, or position. Each option displays an icon (using Elementor's eicon-* classes) and is ideal for design-related choices with 2-4 options.

$this->add_control( 'text_align', [ 'label' => esc_html__('Alignment', 'my-domain'), 'type' => \Elementor\Controls_Manager::CHOOSE, 'options' => [ 'left' => [ 'title' => esc_html__('Left', 'my-domain'), 'icon' => 'eicon-text-align-left', ], 'center' => [ 'title' => esc_html__('Center', 'my-domain'), 'icon' => 'eicon-text-align-center', ], 'right' => [ 'title' => esc_html__('Right', 'my-domain'), 'icon' => 'eicon-text-align-right', ], ], 'default' => 'center', 'toggle' => true, // Allow deselection 'selectors' => [ '{{WRAPPER}} .title' => 'text-align: {{VALUE}};', ], ] );
┌───────┬────────┬───────┐ │ ≡◀ │ ≡ │ ▶≡ │ ← Visual representation │ Left │ Center │ Right │ └───────┴────────┴───────┘

COLOR Control

A color picker with hex, RGB, HSL input support, optional alpha/opacity slider, and global color integration. Use with selectors for direct CSS output; returns empty string if no color selected, hex value (#ffffff), or RGBA for transparent colors.

$this->add_control( 'title_color', [ 'label' => esc_html__('Title Color', 'my-domain'), 'type' => \Elementor\Controls_Manager::COLOR, 'default' => '#333333', 'alpha' => true, // Enable opacity slider 'global' => [ 'default' => \Elementor\Core\Kits\Documents\Tabs\Global_Colors::COLOR_PRIMARY, ], 'selectors' => [ '{{WRAPPER}} .widget-title' => 'color: {{VALUE}};', ], ] );
┌────────────────────┐ │ Title Color │ │ ┌────┐ │ │ │████│ #3366FF │ ← Color picker with swatch │ └────┘ │ │ [Global ▼] │ ← Global colors dropdown └────────────────────┘

DATE_TIME Control

A date and time picker control using Flatpickr library for selecting specific dates, times, or datetime combinations. Configure with picker_options based on Flatpickr's API; returns formatted string that you can parse with PHP's DateTime or strtotime().

$this->add_control( 'event_date', [ 'label' => esc_html__('Event Date', 'my-domain'), 'type' => \Elementor\Controls_Manager::DATE_TIME, 'default' => date('Y-m-d H:i'), 'picker_options' => [ 'enableTime' => true, 'dateFormat' => 'Y-m-d H:i', 'minDate' => 'today', ], ] ); // In render(): $event_date = $settings['event_date']; $timestamp = strtotime($event_date); echo 'Event: ' . date('F j, Y g:i A', $timestamp);
┌───────────────────────────┐ │ Event Date │ │ ┌─────────────────────┐ │ │ │ 2024-12-25 14:30 │ │ │ └─────────────────────┘ │ │ ┌─────────────────────┐ │ │ │ ◀ December 2024 ▶ │ │ │ │ Su Mo Tu We Th Fr Sa│ │ │ │ 1 2 3 4 5 6 7│ │ │ │ 8 9 10 11 12 13 14│ │ │ │ ... [25] ... │ │ │ └─────────────────────┘ │ └───────────────────────────┘

Advanced Controls

MEDIA Control

The MEDIA control allows users to select images or videos from the WordPress Media Library, returning an array with url and id keys for the selected media file.

$this->add_control( 'image', [ 'label' => esc_html__( 'Choose Image', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::MEDIA, 'default' => [ 'url' => \Elementor\Utils::get_placeholder_image_src(), ], ] ); // Usage: $settings['image']['url'], $settings['image']['id']

The GALLERY control enables selection of multiple images from the Media Library, returning an array of image objects, perfect for creating image galleries or sliders.

$this->add_control( 'gallery', [ 'label' => esc_html__( 'Add Images', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::GALLERY, 'default' => [], 'show_label' => false, ] ); // Returns: [ ['id'=>1,'url'=>'...'], ['id'=>2,'url'=>'...'] ]

ICONS Control

The ICONS control provides a comprehensive icon picker supporting Font Awesome, custom icon libraries, and SVG uploads, returning an array with library and value keys.

$this->add_control( 'selected_icon', [ 'label' => esc_html__( 'Icon', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::ICONS, 'default' => [ 'value' => 'fas fa-star', 'library' => 'fa-solid', ], ] ); // Render: \Elementor\Icons_Manager::render_icon( $settings['selected_icon'] );

SLIDER Control

The SLIDER control creates a draggable range slider with numeric input, supporting multiple units (px, em, %), min/max values, and step increments for precise value selection.

$this->add_control( 'width', [ 'label' => esc_html__( 'Width', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::SLIDER, 'size_units' => [ 'px', '%', 'em' ], 'range' => [ 'px' => [ 'min' => 0, 'max' => 1000, 'step' => 5 ], '%' => [ 'min' => 0, 'max' => 100 ], ], 'default' => [ 'unit' => 'px', 'size' => 50 ], 'selectors' => [ '{{WRAPPER}} .element' => 'width: {{SIZE}}{{UNIT}};', ], ] );

DIMENSIONS Control

The DIMENSIONS control provides four linked input fields for padding, margin, or border-radius values with unit selection and the ability to link/unlink values.

$this->add_control( 'margin', [ 'label' => esc_html__( 'Margin', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::DIMENSIONS, 'size_units' => [ 'px', '%', 'em' ], 'selectors' => [ '{{WRAPPER}} .element' => 'margin: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], ] );
┌─────────────────────────────┐
│  ┌───┐ ┌───┐ ┌───┐ ┌───┐   │
│  │ T │ │ R │ │ B │ │ L │ 🔗│
│  └───┘ └───┘ └───┘ └───┘   │
└─────────────────────────────┘

HEADING Control

The HEADING control is a UI-only element that displays a styled heading/label in the editor panel to organize and separate control sections visually.

$this->add_control( 'section_heading', [ 'label' => esc_html__( 'Style Options', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::HEADING, 'separator' => 'before', // 'before', 'after', 'none' ] );

RAW_HTML Control

The RAW_HTML control injects custom HTML content directly into the editor panel, useful for displaying instructions, warnings, or custom UI elements that don't store values.

$this->add_control( 'important_note', [ 'type' => \Elementor\Controls_Manager::RAW_HTML, 'raw' => '<div class="notice"> <strong>Note:</strong> Configure API key in settings first. </div>', 'content_classes' => 'elementor-panel-alert elementor-panel-alert-info', ] );

BUTTON Control

The BUTTON control renders a clickable button in the editor panel that triggers JavaScript events, commonly used for triggering AJAX actions or opening modals.

$this->add_control( 'sync_button', [ 'label' => esc_html__( 'Sync Data', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::BUTTON, 'text' => esc_html__( 'Click to Sync', 'plugin-name' ), 'event' => 'namespace:editor:sync', // Custom JS event ] );

DIVIDER Control

The DIVIDER control adds a horizontal line separator in the editor panel to visually organize controls into logical groups without affecting functionality.

$this->add_control( 'hr', [ 'type' => \Elementor\Controls_Manager::DIVIDER, ] );
├─────────────────────────────┤
│  Control Above              │
├─────────────────────────────┤  ← DIVIDER
│  Control Below              │
└─────────────────────────────┘

DEPRECATED_NOTICE Control

The DEPRECATED_NOTICE control displays a standardized deprecation warning in the editor, informing users that a widget or feature is obsolete and suggesting alternatives.

$this->add_control( 'deprecated_notice', [ 'type' => \Elementor\Controls_Manager::DEPRECATED_NOTICE, 'widget' => 'old-widget-name', 'since' => '3.0.0', 'last' => '4.0.0', 'plugin' => 'My Plugin', 'replacement' => 'new-widget-name', ] );

ALERT Control

The ALERT control displays styled notification messages (info, success, warning, danger) in the editor panel to communicate important information to users.

$this->add_control( 'alert_warning', [ 'type' => \Elementor\Controls_Manager::ALERT, 'alert_type' => 'warning', // info, success, warning, danger 'heading' => esc_html__( 'Warning', 'plugin-name' ), 'content' => esc_html__( 'This requires Pro version.', 'plugin-name' ), ] );

Style Controls

FONT Control

The FONT control provides a searchable dropdown of all available fonts including Google Fonts, system fonts, and custom uploaded fonts, returning the font-family name as a string.

$this->add_control( 'font_family', [ 'label' => esc_html__( 'Font Family', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::FONT, 'default' => "'Open Sans', sans-serif", 'selectors' => [ '{{WRAPPER}} .title' => 'font-family: {{VALUE}}', ], ] );

TYPOGRAPHY Control (Group)

The TYPOGRAPHY group control bundles font-family, size, weight, transform, style, decoration, line-height, and letter-spacing into a single cohesive control set for comprehensive text styling.

$this->add_group_control( \Elementor\Group_Control_Typography::get_type(), [ 'name' => 'content_typography', 'label' => esc_html__( 'Typography', 'plugin-name' ), 'selector' => '{{WRAPPER}} .content-text', ] );
┌─ Typography ─────────────────┐
│ Font Family: [Roboto     ▼] │
│ Size:        [16] [px ▼]    │
│ Weight:      [400      ▼]   │
│ Transform:   [None     ▼]   │
│ Line Height: [1.5]          │
│ Letter Spacing: [0]         │
└──────────────────────────────┘

TEXT_SHADOW Control (Group)

The TEXT_SHADOW group control provides color, blur, horizontal, and vertical offset settings to apply CSS text-shadow effects to text elements.

$this->add_group_control( \Elementor\Group_Control_Text_Shadow::get_type(), [ 'name' => 'text_shadow', 'label' => esc_html__( 'Text Shadow', 'plugin-name' ), 'selector' => '{{WRAPPER}} .heading', ] ); // Output: text-shadow: 2px 2px 4px rgba(0,0,0,0.5);

BOX_SHADOW Control (Group)

The BOX_SHADOW group control enables configuration of horizontal/vertical offsets, blur, spread, color, and inset options for CSS box-shadow effects on container elements.

$this->add_group_control( \Elementor\Group_Control_Box_Shadow::get_type(), [ 'name' => 'box_shadow', 'label' => esc_html__( 'Box Shadow', 'plugin-name' ), 'selector' => '{{WRAPPER}} .card', ] ); // Output: box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1);

BORDER Control (Group)

The BORDER group control combines border-style (solid, dashed, dotted, etc.), border-width for each side, and border-color into a unified control for element borders.

$this->add_group_control( \Elementor\Group_Control_Border::get_type(), [ 'name' => 'border', 'label' => esc_html__( 'Border', 'plugin-name' ), 'selector' => '{{WRAPPER}} .box', ] );

BACKGROUND Control (Group)

The BACKGROUND group control supports classic (color/image), gradient, video, and slideshow background types with positioning, sizing, attachment, and overlay options.

$this->add_group_control( \Elementor\Group_Control_Background::get_type(), [ 'name' => 'background', 'label' => esc_html__( 'Background', 'plugin-name' ), 'types' => [ 'classic', 'gradient', 'video' ], 'selector' => '{{WRAPPER}} .section', ] );
Background Type: [Classic] [Gradient] [Video]
┌────────────────────────────────────┐
│ Color: [████████] Image: [Choose] │
│ Position: [Center ▼]              │
│ Size: [Cover ▼]                   │
└────────────────────────────────────┘

CSS_FILTER Control (Group)

The CSS_FILTER group control provides blur, brightness, contrast, saturation, and hue rotation sliders to apply CSS filter effects to images or elements.

$this->add_group_control( \Elementor\Group_Control_Css_Filter::get_type(), [ 'name' => 'css_filters', 'selector' => '{{WRAPPER}} img', ] ); // Output: filter: blur(2px) brightness(110%) contrast(90%);

IMAGE_DIMENSIONS Control

The IMAGE_DIMENSIONS control provides width and height input fields specifically for controlling image dimensions, with options to maintain aspect ratio.

$this->add_control( 'image_size', [ 'label' => esc_html__( 'Image Dimensions', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::IMAGE_DIMENSIONS, 'default' => [ 'width' => '300', 'height' => '200', ], ] ); // Access: $settings['image_size']['width'], $settings['image_size']['height']

Responsive Controls

add_responsive_control()

The add_responsive_control() method automatically creates device-specific variants (desktop, tablet, mobile) of any control, allowing different values for each breakpoint.

$this->add_responsive_control( 'content_align', [ 'label' => esc_html__( 'Alignment', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::CHOOSE, 'options' => [ 'left' => [ 'title' => 'Left', 'icon' => 'eicon-text-align-left' ], 'center' => [ 'title' => 'Center', 'icon' => 'eicon-text-align-center' ], 'right' => [ 'title' => 'Right', 'icon' => 'eicon-text-align-right' ], ], 'devices' => [ 'desktop', 'tablet', 'mobile' ], 'selectors' => [ '{{WRAPPER}} .content' => 'text-align: {{VALUE}};', ], ] ); // Creates: content_align, content_align_tablet, content_align_mobile

Responsive Selectors

Responsive selectors automatically generate device-specific CSS using Elementor's breakpoint system, applying styles wrapped in appropriate media queries.

$this->add_responsive_control( 'column_width', [ 'label' => esc_html__( 'Width', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::SLIDER, 'range' => [ 'px' => [ 'min' => 0, 'max' => 100 ] ], 'selectors' => [ '{{WRAPPER}} .col' => 'width: {{SIZE}}%;', ], ] );
Generated CSS:
─────────────────────────────────────
Desktop: .col { width: 33%; }
@media (max-width: 1024px) { .col { width: 50%; } }
@media (max-width: 767px)  { .col { width: 100%; } }

Device-Specific Values

Access device-specific values in render methods using the _tablet and _mobile suffixes appended to control names, with fallback logic for inheritance.

protected function render() { $settings = $this->get_settings_for_display(); // Desktop value $columns_desktop = $settings['columns']; // Tablet value (with fallback to desktop) $columns_tablet = $settings['columns_tablet'] ?: $columns_desktop; // Mobile value (with fallback chain) $columns_mobile = $settings['columns_mobile'] ?: $columns_tablet; // Dynamic output with device detection echo '<div data-cols="' . esc_attr( $columns_desktop ) . '" data-cols-tablet="' . esc_attr( $columns_tablet ) . '" data-cols-mobile="' . esc_attr( $columns_mobile ) . '">'; }

Responsive Conditions

Responsive conditions allow controls to be shown or hidden based on device preview mode or other control values, using the conditions parameter.

$this->add_control( 'mobile_menu_icon', [ 'label' => esc_html__( 'Menu Icon', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::ICONS, 'condition' => [ 'show_mobile_menu' => 'yes', ], 'responsive' => [ 'mobile' => [ 'condition' => [ 'layout!' => 'inline', ], ], ], ] ); // Device-specific visibility $this->add_control( 'desktop_only_setting', [ 'label' => 'Desktop Only', 'type' => \Elementor\Controls_Manager::TEXT, 'devices' => [ 'desktop' ], // Only show for desktop ] );

Group Controls

add_group_control()

The add_group_control() method registers a predefined set of related controls as a single unit, automatically handling naming prefixes, rendering, and CSS output.

$this->add_group_control( \Elementor\Group_Control_Typography::get_type(), // Control type [ 'name' => 'title_typography', // Unique prefix 'label' => esc_html__( 'Typography', 'plugin-name' ), 'selector' => '{{WRAPPER}} .title', 'exclude' => [ 'font_style' ], // Exclude specific fields 'fields_options' => [ 'font_size' => [ 'default' => [ 'size' => 24 ] ], ], ] );

Group_Control_Typography

Group_Control_Typography bundles 10+ typography-related settings including font family, size, weight, transform, style, decoration, line-height, letter-spacing, and word-spacing.

$this->add_group_control( \Elementor\Group_Control_Typography::get_type(), [ 'name' => 'heading_typography', 'scheme' => \Elementor\Core\Schemes\Typography::TYPOGRAPHY_1, 'selector' => '{{WRAPPER}} h2', 'fields_options' => [ 'typography' => [ 'default' => 'yes' ], 'font_weight' => [ 'default' => '700' ], ], ] );

Group_Control_Text_Shadow

Group_Control_Text_Shadow provides horizontal offset, vertical offset, blur radius, and color controls to create text shadow effects on typography elements.

$this->add_group_control( \Elementor\Group_Control_Text_Shadow::get_type(), [ 'name' => 'title_shadow', 'selector' => '{{WRAPPER}} .title', 'fields_options' => [ 'text_shadow' => [ 'default' => [ 'horizontal' => 2, 'vertical' => 2, 'blur' => 4, 'color' => 'rgba(0,0,0,0.3)', ], ], ], ] );

Group_Control_Box_Shadow

Group_Control_Box_Shadow includes color, horizontal, vertical, blur, spread, and position (inset/outset) controls for comprehensive box-shadow styling.

$this->add_group_control( \Elementor\Group_Control_Box_Shadow::get_type(), [ 'name' => 'card_shadow', 'label' => esc_html__( 'Box Shadow', 'plugin-name' ), 'selector' => '{{WRAPPER}} .card', 'fields_options' => [ 'box_shadow_type' => [ 'default' => 'yes' ], 'box_shadow' => [ 'default' => [ 'horizontal' => 0, 'vertical' => 10, 'blur' => 20, 'spread' => 0, 'color' => 'rgba(0,0,0,0.15)', ], ], ], ] );

Group_Control_Border

Group_Control_Border combines border type (solid, dashed, dotted, double, none), width controls for each side, and a color picker for unified border management.

$this->add_group_control( \Elementor\Group_Control_Border::get_type(), [ 'name' => 'box_border', 'label' => esc_html__( 'Border', 'plugin-name' ), 'selector' => '{{WRAPPER}} .box', 'fields_options' => [ 'border' => [ 'default' => 'solid' ], 'width' => [ 'default' => [ 'top' => '1', 'right' => '1', 'bottom' => '1', 'left' => '1', ], ], 'color' => [ 'default' => '#E0E0E0' ], ], ] );

Group_Control_Background

Group_Control_Background supports classic (solid color + image), gradient (linear/radial), video backgrounds, and slideshow with comprehensive positioning and overlay controls.

$this->add_group_control( \Elementor\Group_Control_Background::get_type(), [ 'name' => 'section_bg', 'types' => [ 'classic', 'gradient' ], 'exclude' => [ 'image' ], // Remove image option 'selector' => '{{WRAPPER}} .section', 'fields_options' => [ 'background' => [ 'default' => 'gradient' ], 'color' => [ 'default' => '#6EC1E4' ], 'color_b' => [ 'default' => '#54595F' ], 'gradient_angle' => [ 'default' => [ 'size' => 180 ] ], ], ] );

Group_Control_Css_Filter

Group_Control_Css_Filter provides slider controls for blur, brightness, contrast, saturation, and hue-rotate CSS filter properties.

$this->add_group_control( \Elementor\Group_Control_Css_Filter::get_type(), [ 'name' => 'image_filters', 'selector' => '{{WRAPPER}} .image-wrapper img', ] ); // Hover state filters $this->add_group_control( \Elementor\Group_Control_Css_Filter::get_type(), [ 'name' => 'image_filters_hover', 'selector' => '{{WRAPPER}} .image-wrapper:hover img', ] );

Group_Control_Image_Size

Group_Control_Image_Size provides a dropdown of registered WordPress image sizes plus custom dimensions option for flexible image sizing.

$this->add_group_control( \Elementor\Group_Control_Image_Size::get_type(), [ 'name' => 'thumbnail', 'default' => 'medium', 'exclude' => [ 'custom' ], ] ); // In render(): $image_html = \Elementor\Group_Control_Image_Size::get_attachment_image_html( $settings, 'thumbnail', 'image' ); echo $image_html;

Creating Custom Group Controls

Custom group controls extend \Elementor\Group_Control_Base to bundle multiple related controls into a reusable unit with shared logic and CSS output.

class Group_Control_Custom_Overlay extends \Elementor\Group_Control_Base { public static function get_type() { return 'custom-overlay'; } protected function init_fields() { $fields = []; $fields['color'] = [ 'label' => esc_html__( 'Overlay Color', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::COLOR, 'selectors' => [ '{{SELECTOR}}::before' => 'background-color: {{VALUE}};', ], ]; $fields['opacity'] = [ 'label' => esc_html__( 'Opacity', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::SLIDER, 'range' => [ 'px' => [ 'min' => 0, 'max' => 1, 'step' => 0.1 ] ], 'selectors' => [ '{{SELECTOR}}::before' => 'opacity: {{SIZE}};', ], ]; return $fields; } protected function get_default_options() { return [ 'popover' => [ 'starter_name' => 'overlay', 'starter_title' => esc_html__( 'Overlay', 'plugin-name' ), ], ]; } } // Register: Plugin::instance()->controls_manager->add_group_control( 'custom-overlay', new Group_Control_Custom_Overlay() );

Repeater Control

Repeater Class

The Repeater class creates dynamic, sortable lists of items where each item contains a set of controls, enabling users to add, remove, and reorder entries.

$repeater = new \Elementor\Repeater(); $repeater->add_control( 'item_title', [ 'label' => esc_html__( 'Title', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::TEXT, 'default' => esc_html__( 'Item Title', 'plugin-name' ), ] ); $this->add_control( 'items_list', [ 'label' => esc_html__( 'Items', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::REPEATER, 'fields' => $repeater->get_controls(), 'default' => [ [ 'item_title' => 'Item #1' ], [ 'item_title' => 'Item #2' ], ], 'title_field' => '{{{ item_title }}}', ] );
┌─────────────────────────────────┐
│ [≡] Item #1                   ✕ │
├─────────────────────────────────┤
│ [≡] Item #2                   ✕ │
├─────────────────────────────────┤
│ [≡] Item #3                   ✕ │
└─────────────────────────────────┘
        [+ Add Item]

Adding Repeater Controls

Repeater controls support all standard control types including text, media, icons, selects, and even group controls, added via the Repeater instance before registration.

$repeater = new \Elementor\Repeater(); $repeater->add_control( 'tab_title', [ 'label' => esc_html__( 'Tab Title', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::TEXT, 'default' => 'Tab Title', ] ); $repeater->add_control( 'tab_icon', [ 'label' => esc_html__( 'Icon', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::ICONS, ] ); $repeater->add_control( 'tab_content', [ 'label' => esc_html__( 'Content', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::WYSIWYG, ] ); // Add group control to repeater $repeater->add_group_control( \Elementor\Group_Control_Typography::get_type(), [ 'name' => 'tab_typography', 'selector' => '{{WRAPPER}} {{CURRENT_ITEM}} .tab-text', ] );

Rendering Repeater Data

Render repeater data by looping through the items array, using {{CURRENT_ITEM}} selector placeholder for item-specific CSS targeting.

protected function render() { $settings = $this->get_settings_for_display(); if ( empty( $settings['items_list'] ) ) { return; } echo '<div class="items-wrapper">'; foreach ( $settings['items_list'] as $index => $item ) { $item_class = 'elementor-repeater-item-' . esc_attr( $item['_id'] ); ?> <div class="item <?php echo $item_class; ?>"> <h3><?php echo esc_html( $item['item_title'] ); ?></h3> <p><?php echo wp_kses_post( $item['item_content'] ); ?></p> <?php \Elementor\Icons_Manager::render_icon( $item['item_icon'] ); ?> </div> <?php } echo '</div>'; }

Repeater with Conditions

Conditions within repeaters show/hide controls based on other repeater field values, using standard condition syntax targeting sibling controls.

$repeater = new \Elementor\Repeater(); $repeater->add_control( 'link_type', [ 'label' => esc_html__( 'Link Type', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::SELECT, 'options' => [ 'none' => 'None', 'url' => 'URL', 'page' => 'Page', ], 'default' => 'none', ] ); $repeater->add_control( 'link_url', [ 'label' => esc_html__( 'URL', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::URL, 'condition' => [ 'link_type' => 'url', // Only show when URL selected ], ] ); $repeater->add_control( 'link_page', [ 'label' => esc_html__( 'Page', 'plugin-name' ), 'type' => \Elementor\Controls_Manager::SELECT2, 'options' => $this->get_pages_list(), 'condition' => [ 'link_type' => 'page', // Only show when Page selected ], ] );

Nested Repeaters

Nested repeaters (repeaters within repeaters) require careful implementation and are generally handled through workarounds since Elementor doesn't natively support deep nesting.

// Primary Repeater (Parent) $parent_repeater = new \Elementor\Repeater(); $parent_repeater->add_control( 'section_title', [ 'label' => 'Section Title', 'type' => \Elementor\Controls_Manager::TEXT, ] ); // Child items as TEXTAREA with JSON (workaround) $parent_repeater->add_control( 'child_items', [ 'label' => 'Sub Items', 'type' => \Elementor\Controls_Manager::TEXTAREA, 'description' => 'Enter items, one per line', ] ); // Alternative: Use GALLERY for nested images $parent_repeater->add_control( 'nested_gallery', [ 'label' => 'Images', 'type' => \Elementor\Controls_Manager::GALLERY, ] ); $this->add_control( 'sections', [ 'label' => 'Sections', 'type' => \Elementor\Controls_Manager::REPEATER, 'fields' => $parent_repeater->get_controls(), ] ); // Render nested structure protected function render() { foreach ( $settings['sections'] as $section ) { echo '<div class="section">'; echo '<h2>' . esc_html( $section['section_title'] ) . '</h2>'; // Parse child items from textarea $children = explode( "\n", $section['child_items'] ); echo '<ul>'; foreach ( $children as $child ) { echo '<li>' . esc_html( trim( $child ) ) . '</li>'; } echo '</ul></div>'; } }
┌─ Parent Repeater ────────────────┐
│  ┌─ Item 1 ───────────────────┐ │
│  │ Title: [Section A        ] │ │
│  │ ┌─ Nested Items ─────────┐ │ │
│  │ │ • Sub-item 1           │ │ │
│  │ │ • Sub-item 2           │ │ │
│  │ └────────────────────────┘ │ │
│  └────────────────────────────┘ │
│  ┌─ Item 2 ───────────────────┐ │
│  │ ...                        │ │
│  └────────────────────────────┘ │
└──────────────────────────────────┘

Quick Reference Chart

┌─────────────────────────────────────────────────────────────────┐
│                    ELEMENTOR CONTROLS HIERARCHY                 │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌─ Basic Controls ─────┐  ┌─ Advanced Controls ─────────────┐ │
│  │ • TEXT               │  │ • MEDIA (image/video selector) │ │
│  │ • TEXTAREA           │  │ • GALLERY (multi-image)        │ │
│  │ • NUMBER             │  │ • ICONS (icon picker)          │ │
│  │ • SELECT             │  │ • SLIDER (range input)         │ │
│  │ • SWITCHER           │  │ • DIMENSIONS (margin/padding)  │ │
│  │ • COLOR              │  │ • REPEATER (dynamic lists)     │ │
│  └──────────────────────┘  └─────────────────────────────────┘ │
│                                                                 │
│  ┌─ Group Controls ──────────────────────────────────────────┐ │
│  │ Typography │ Background │ Border │ Box Shadow │ CSS Filter│ │
│  └───────────────────────────────────────────────────────────┘ │
│                                                                 │
│  ┌─ Responsive Layer ────────────────────────────────────────┐ │
│  │     Desktop          Tablet           Mobile              │ │
│  │    (>1024px)      (768-1024px)       (<768px)             │ │
│  └───────────────────────────────────────────────────────────┘ │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘