tools/elementor-widget-generator
Elementor Widget Generator
Build an Elementor widget class and the few lines that register it: content controls the template reads, style controls Elementor turns into CSS, responsive values, typography, and a panel category of your own — on the hooks Elementor actually documents today.
widgets/class-my-widget-widget.php
<?php
/**
* Elementor widget: My Widget
*
* Generated by wp-skills.com
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* My Widget.
*
* Only get_name() is abstract on \Elementor\Widget_Base — it is declared on
* Controls_Stack, and everything else below has a working implementation
* upstream. Verified against Elementor 4.2.4, where a subclass declaring
* nothing but get_name() registered successfully and came back with an empty
* title, the icon "eicon-apps" and the category "general". An empty title is
* why get_title() is here regardless: the widget would otherwise sit nameless
* in the panel.
*/
class My_Widget_Widget extends \Elementor\Widget_Base {
/**
* The key Elementor stores this widget under, and the value saved into
* every page that uses it. Changing it later orphans those pages.
*/
public function get_name(): string {
return 'my_widget';
}
public function get_title(): string {
return __( 'My Widget', 'text-domain' );
}
/**
* Any Elementor icon class. The default for a widget that does not say is
* "eicon-apps".
*/
public function get_icon(): string {
return 'eicon-post-content';
}
/**
* The panel section this widget appears under.
*
* A slug nobody registered is not an error — the widget still registers,
* and its config still carries the slug. It simply belongs to no section,
* so nothing in the editor shows it. Measured in 4.2.4.
*/
public function get_categories(): array {
return array( 'general' );
}
/**
* register_controls(), without the leading underscore.
*
* _register_controls() still exists and still runs — Elementor deprecated
* it in 3.1.0 rather than removing it, and Controls_Stack::init_controls()
* calls whichever one the subclass defined, raising a deprecation notice
* for the old name. Verified in 4.2.4.
*
* There is no Advanced tab here on purpose: Elementor adds it itself. A
* widget declaring no controls at all already carries 166 of them —
* measured — covering margin, padding, positioning, motion effects,
* responsive visibility and custom CSS.
*/
protected function register_controls(): void {
$this->start_controls_section(
'section_content',
array(
'label' => __( 'Content', 'text-domain' ),
'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
)
);
$this->add_control(
'heading',
array(
'label' => __( 'Heading', 'text-domain' ),
'type' => \Elementor\Controls_Manager::TEXT,
'default' => __( 'Hello', 'text-domain' ),
)
);
$this->end_controls_section();
$this->start_controls_section(
'section_style',
array(
'label' => __( 'Style', 'text-domain' ),
'tab' => \Elementor\Controls_Manager::TAB_STYLE,
)
);
$this->add_control(
'text_colour',
array(
'label' => __( 'Text colour', 'text-domain' ),
'type' => \Elementor\Controls_Manager::COLOR,
'selectors' => array(
'{{WRAPPER}} .my-widget' => 'color: {{VALUE}};',
),
)
);
$this->add_group_control(
\Elementor\Group_Control_Typography::get_type(),
array(
'name' => 'my_widget_typography',
'selector' => '{{WRAPPER}} .my-widget',
)
);
$this->end_controls_section();
}
/**
* The front end.
*
* get_settings_for_display() rather than get_settings(): it is the one that
* resolves dynamic tags and the responsive value for the current device.
*/
protected function render(): void {
$settings = $this->get_settings_for_display();
?>
<div class="my-widget">
<p class="my-widget__heading"><?php echo esc_html( $settings['heading'] ); ?></p>
</div>
<?php
}
}