Skip to content

Alles für WordPress, Webentwicklung — und mehr

🛠️ Benutzerdefinierte Elementor-Widgets: vollständiger Zyklus vom Plugin bis zu den Steuerelementen

🛠️ Benutzerdefinierte Elementor-Widgets: vollständiger Zyklus vom Plugin bis zu den Steuerelementen

Das Theme-Update hat das benutzerdefinierte Block-Widget zerschossen, das auf der Startseite die Werbeaktionen anzeigte. Kommt Ihnen das bekannt vor? Der Code steckt in functions.php, die Styles sind über style.css verstreut und der JS-Handler ist an footer.php genagelt. Wechseln Sie das Theme oder führen ein größeres Elementor-Update durch, müssen Sie all das von Grund auf neu zusammensetzen.

Das Problem ist nicht, dass „der Code schlecht ist". Das Problem ist, dass er am falschen Ort lebt. Benutzerdefinierte Anpassungen, die ins Theme gestopft werden, sind dauerhaft daran gebunden: Eine Übertragung auf eine andere Website ist unmöglich, die Fehlersuche bei Konflikten mühsam und ein bedingtes Laden von Assets existiert nicht.

Der richtige Ansatz ist, das benutzerdefinierte Widget als eigenständiges Plugin zu paketieren. Genau das empfiehlt die offizielle Elementor-Dokumentation für alle nicht-trivialen Anpassungen. Code-Isolation, Theme-Unabhängigkeit, ordentliche Versionierung und JS-Laden nur auf Seiten, die das Widget enthalten. In dieser Anleitung der vollständige Zyklus: von der Plugin-Struktur bis zum live-fähigen Widget mit Inline-Editing auf Basis der aktuellen Elementor-API (3.x/4.x).

Code-Editoren auf einem Computerbildschirm

💡 Kurzüberblick:

  • Registrieren Sie ein separates Plugin für das Widget und isolieren Sie den Code ein für alle Mal vom Theme.
  • Bauen Sie die Hauptklasse mit Kompatibilitätsprüfungen: Elementor aktiv, Version nicht niedriger als 3.5, PHP ab 7.4.
  • Erstellen Sie die Widget-Klasse: Erweitern Sie Widget_Base, definieren Sie register_controls() und die Render-Methode.
  • Verdrahten Sie die Inline-Textbearbeitung über add_inline_editing_attributes() und ein Backbone-Template.
  • Fügen Sie benutzerdefinierte Felder hinzu: Bildauswahl aus der Medienbibliothek, Dropdowns, Button-Gruppen, Typografie.
  • Hängen Sie den JavaScript-Handler an elementor/frontend/init, den Einstiegspunkt für jegliche Client-Logik.

Plugin-Struktur und Hauptklasse

Wir erstellen das Plugin Elementor Awesomesauce. Minimale Dateistruktur:

1elementor-awesomesauce/
2├── elementor-awesomesauce.php ← entry point
3├── plugin.php ← plugin singleton class
4├── widgets/
5│ └── awesomesauce.php ← widget class
6└── assets/
7 └── js/
8 └── awesomesauce.js ← frontend JS handler

Haupt-Plugin-Datei, standardmäßiger WordPress-Header plus eine Loader-Klasse mit Kompatibilitätsprüfungen. Das ist exakt die Struktur, die im Elementor-Leitfaden für das erste Addon beschrieben wird. Der folgende Code funktioniert in Elementor 3.5+ und Elementor 4.x: Methoden ohne das veraltete Präfix _, Konstanten für moderne PHP-Versionen.

Fügen Sie diesen Code in elementor-awesomesauce.php ein. Die Datei gehört nach /wp-content/plugins/elementor-awesomesauce/. Erstellen Sie ein vollständiges Site-Backup, bevor Sie das Plugin aktivieren.

1<?php
2/**
3 * Plugin Name: Elementor Awesomesauce
4 * Description: Custom Elementor widget with live editing of text, images and styles.
5 * Plugin URI: https://techblog.sdstudio.top/
6 * Version: 1.0.0
7 * Author: TechBlog SD Studio
8 * Text Domain: elementor-awesomesauce
9 */
10
11if ( ! defined( 'ABSPATH' ) ) exit;
12
13final class Elementor_Awesomesauce {
14
15 const VERSION = '1.0.0';
16 const MINIMUM_ELEMENTOR_VERSION = '3.5.0';
17 const MINIMUM_PHP_VERSION = '7.4';
18
19 public function __construct() {
20 add_action( 'init', array( $this, 'i18n' ) );
21 add_action( 'plugins_loaded', array( $this, 'init' ) );
22 }
23
24 public function i18n() {
25 load_plugin_textdomain( 'elementor-awesomesauce' );
26 }
27
28 public function init() {
29 if ( ! did_action( 'elementor/loaded' ) ) {
30 add_action( 'admin_notices', array( $this, 'admin_notice_missing_main_plugin' ) );
31 return;
32 }
33
34 if ( ! version_compare( ELEMENTOR_VERSION, self::MINIMUM_ELEMENTOR_VERSION, '>=' ) ) {
35 add_action( 'admin_notices', array( $this, 'admin_notice_minimum_elementor_version' ) );
36 return;
37 }
38
39 if ( version_compare( PHP_VERSION, self::MINIMUM_PHP_VERSION, '<' ) ) {
40 add_action( 'admin_notices', array( $this, 'admin_notice_minimum_php_version' ) );
41 return;
42 }
43
44 require_once( 'plugin.php' );
45 }
46
47 public function admin_notice_missing_main_plugin() {
48 if ( isset( $_GET['activate'] ) ) {
49 unset( $_GET['activate'] );
50 }
51 $message = sprintf(
52 esc_html__( '"%1$s" requires "%2$s" to be installed and activated.', 'elementor-awesomesauce' ),
53 '<strong>' . esc_html__( 'Elementor Awesomesauce', 'elementor-awesomesauce' ) . '</strong>',
54 '<strong>' . esc_html__( 'Elementor', 'elementor-awesomesauce' ) . '</strong>'
55 );
56 printf( '<p>%1$s</p>', $message );
57 }
58
59 public function admin_notice_minimum_elementor_version() {
60 if ( isset( $_GET['activate'] ) ) {
61 unset( $_GET['activate'] );
62 }
63 $message = sprintf(
64 esc_html__( '"%1$s" requires "%2$s" version %3$s or greater.', 'elementor-awesomesauce' ),
65 '<strong>' . esc_html__( 'Elementor Awesomesauce', 'elementor-awesomesauce' ) . '</strong>',
66 '<strong>' . esc_html__( 'Elementor', 'elementor-awesomesauce' ) . '</strong>',
67 self::MINIMUM_ELEMENTOR_VERSION
68 );
69 printf( '<p>%1$s</p>', $message );
70 }
71
72 public function admin_notice_minimum_php_version() {
73 if ( isset( $_GET['activate'] ) ) {
74 unset( $_GET['activate'] );
75 }
76 $message = sprintf(
77 esc_html__( '"%1$s" requires "%2$s" version %3$s or greater.', 'elementor-awesomesauce' ),
78 '<strong>' . esc_html__( 'Elementor Awesomesauce', 'elementor-awesomesauce' ) . '</strong>',
79 '<strong>' . esc_html__( 'PHP', 'elementor-awesomesauce' ) . '</strong>',
80 self::MINIMUM_PHP_VERSION
81 );
82 printf( '<p>%1$s</p>', $message );
83 }
84}
85
86new Elementor_Awesomesauce();

Worauf es hier ankommt. Die Konstante MINIMUM_ELEMENTOR_VERSION, 3.5.0. Ab dieser Version gelten neue Konventionen für Hook- und Methodennamen ohne das Präfix _. Für Elementor 4.x (aktuelle Version mit Stand Juni 2026) funktioniert derselbe Code unverändert, die Widget-API in 4.x wurde nicht gebrochen. PHP mindestens 7.4, in der Praxis ist 8.x jedoch bereits der De-facto-Standard. Die Klasse ist als final deklariert: Dies ist der Einstiegspunkt, es besteht keine Notwendigkeit, davon zu erben.

Plugin-Klasse: Singleton und Widget-Registrierung

Datei plugin.php, ein Singleton, das JS-Assets lädt und das Widget über den Hook elementor/widgets/register registriert. Das ist die wesentliche Änderung gegenüber älteren Anleitungen: Der Hook elementor/widgets/widgets_registered wurde seit Version 3.5.0 als veraltet gekennzeichnet, die Methode register_widget_type() wurde durch register() ersetzt.

1<?php
2namespace ElementorAwesomesauce;
3
4use Elementor\Plugin as ElementorPlugin;
5
6class Plugin {
7
8 private static $_instance = null;
9
10 public static function instance() {
11 if ( is_null( self::$_instance ) ) {
12 self::$_instance = new self();
13 }
14 return self::$_instance;
15 }
16
17 public function widget_scripts() {
18 wp_register_script(
19 'elementor-awesomesauce',
20 plugins_url( '/assets/js/awesomesauce.js', __FILE__ ),
21 [ 'jquery' ],
22 false,
23 true
24 );
25 }
26
27 private function include_widgets_files() {
28 require_once( __DIR__ . '/widgets/awesomesauce.php' );
29 }
30
31 public function register_widgets( $widgets_manager ) {
32 $this->include_widgets_files();
33 $widgets_manager->register( new \ElementorAwesomesauceWidgets\Awesomesauce() );
34 }
35
36 public function __construct() {
37 add_action( 'elementor/frontend/after_register_scripts', [ $this, 'widget_scripts' ] );
38 add_action( 'elementor/widgets/register', [ $this, 'register_widgets' ] );
39 }
40}
41
42Plugin::instance();

Beachten Sie: register_widgets akzeptiert einen Parameter $widgets_manager. Im neuen Hook wird der Manager direkt übergeben und nicht über ElementorPlugin::instance()->widgets_manager bezogen. Sauberer und ohne den zusätzlichen Import.

Widget-Klasse: Vererbung, Controls und Rendering

Datei widgets/awesomesauce.php, das Herzstück des Plugins. Erweitern Sie Widget_Base, definieren Sie Name, Titel, Icon und Kategorie. Die Methode register_controls() fügt drei Textfelder hinzu: einzeilig, mehrzeilig und WYSIWYG. Die Methode render() gibt das Markup im Frontend aus, und content_template() definiert das Backbone-Template für die Live-Vorschau im Editor.

1<?php
2namespace ElementorAwesomesauceWidgets;
3
4use Elementor\Widget_Base;
5use Elementor\Controls_Manager;
6
7if ( ! defined( 'ABSPATH' ) ) exit;
8
9class Awesomesauce extends Widget_Base {
10
11 public function get_name() {
12 return 'awesomesauce';
13 }
14
15 public function get_title() {
16 return __( 'Awesomesauce', 'elementor-awesomesauce' );
17 }
18
19 public function get_icon() {
20 return 'eicon-pencil';
21 }
22
23 public function get_categories() {
24 return [ 'general' ];
25 }
26
27 public function get_keywords() {
28 return [ 'awesomesauce', 'custom', 'demo' ];
29 }
30
31 protected function register_controls() {
32 $this->start_controls_section(
33 'section_content',
34 [
35 'label' => __( 'Content', 'elementor-awesomesauce' ),
36 ]
37 );
38
39 $this->add_control(
40 'title',
41 [
42 'label' => __( 'Title', 'elementor-awesomesauce' ),
43 'type' => Controls_Manager::TEXT,
44 'default' => __( 'Title', 'elementor-awesomesauce' ),
45 ]
46 );
47
48 $this->add_control(
49 'description',
50 [
51 'label' => __( 'Description', 'elementor-awesomesauce' ),
52 'type' => Controls_Manager::TEXTAREA,
53 'default' => __( 'Description', 'elementor-awesomesauce' ),
54 ]
55 );
56
57 $this->add_control(
58 'content',
59 [
60 'label' => __( 'Content', 'elementor-awesomesauce' ),
61 'type' => Controls_Manager::WYSIWYG,
62 'default' => __( 'Content', 'elementor-awesomesauce' ),
63 ]
64 );
65
66 $this->end_controls_section();
67 }
68
69 protected function render() {
70 $settings = $this->get_settings_for_display();
71
72 $this->add_inline_editing_attributes( 'title', 'none' );
73 $this->add_inline_editing_attributes( 'description', 'basic' );
74 $this->add_inline_editing_attributes( 'content', 'advanced' );
75 ?>
76 <div class="elementor-awesomesauce">
77 <h2 <?php $this->print_render_attribute_string( 'title' ); ?>>
78 <?php $this->print_unescaped_setting( 'title' ); ?>
79 </h2>
80 <div <?php $this->print_render_attribute_string( 'description' ); ?>>
81 <?php $this->print_unescaped_setting( 'description' ); ?>
82 </div>
83 <div <?php $this->print_render_attribute_string( 'content' ); ?>>
84 <?php $this->print_unescaped_setting( 'content' ); ?>
85 </div>
86 </div>
87 <?php
88 }
89
90 protected function content_template() {
91 ?>
92 <#
93 view.addInlineEditingAttributes( 'title', 'none' );
94 view.addInlineEditingAttributes( 'description', 'basic' );
95 view.addInlineEditingAttributes( 'content', 'advanced' );
96 #>
97 <div class="elementor-awesomesauce">
98 <h2 {{{ view.getRenderAttributeString( 'title' ) }}}>{{{ settings.title }}}</h2>
99 <div {{{ view.getRenderAttributeString( 'description' ) }}}>{{{ settings.description }}}</div>
100 <div {{{ view.getRenderAttributeString( 'content' ) }}}>{{{ settings.content }}}</div>
101 </div>
102 <?php
103 }
104}

Was sich gegenüber veralteten Anleitungen geändert hat. Die Methoden register_controls() und content_template() werden ohne den Unterstrich-Präfix _ geschrieben, dies ist eine Änderung seit Elementor 3.1. Verwenden Sie für die Ausgabe in render() die Funktionen print_render_attribute_string() und print_unescaped_setting() anstelle von direktem echo, ein moderner Ansatz, der seit Elementor 3.x empfohlen wird und in 4.x funktioniert. Das Icon wurde von fa fa-pencil (Font Awesome 4, aus dem Kern entfernt) auf eicon-pencil aus dem nativen Set von Elementor umgestellt.

Benutzerdefinierte Felder: Medien, Auswahllisten, Typografie

Textfelder schränken die Möglichkeiten nicht ein. Wir behandeln vier Steuerelementtypen, die die meisten realen Szenarien abdecken.

Medienfeld

Controls_Manager::MEDIA fügt die standardmäßige Bildauswahl aus der WordPress-Mediathek hinzu. Die Methode Utils::get_placeholder_image_src() funktioniert in aktuellen Versionen und stellt einen grauen Platzhalter bereit, wenn kein Bild ausgewählt ist.

1$this->add_control(
2 'mask_image',
3 [
4 'label' => __( 'Mask Image', 'elementor-awesomesauce' ),
5 'type' => Controls_Manager::MEDIA,
6 'default' => [
7 'url' => \Elementor\Utils::get_placeholder_image_src(),
8 ],
9 ]
10);

Controls_Manager::SELECT, Auswahl aus vordefinierten Werten. Nachfolgend ein Beispiel für das HTML-Tag einer Überschrift:

1$this->add_control(
2 'title_tag',
3 [
4 'label' => __( 'Title HTML Tag', 'elementor-awesomesauce' ),
5 'type' => Controls_Manager::SELECT,
6 'default' => 'h2',
7 'options' => [
8 'h1' => 'H1',
9 'h2' => 'H2',
10 'h3' => 'H3',
11 'h4' => 'H4',
12 ],
13 ]
14);

Schaltflächengruppe

Controls_Manager::CHOOSE zeigt eine Reihe von Icons zur visuellen Auswahl an. Icons, ausschließlich aus dem eicon-*-Set, nicht fa fa-*:

1$this->add_control(
2 'text_align',
3 [
4 'label' => __( 'Alignment', 'elementor-awesomesauce' ),
5 'type' => Controls_Manager::CHOOSE,
6 'options' => [
7 'left' => [
8 'title' => __( 'Left', 'elementor-awesomesauce' ),
9 'icon' => 'eicon-text-align-left',
10 ],
11 'center' => [
12 'title' => __( 'Center', 'elementor-awesomesauce' ),
13 'icon' => 'eicon-text-align-center',
14 ],
15 'right' => [
16 'title' => __( 'Right', 'elementor-awesomesauce' ),
17 'icon' => 'eicon-text-align-right',
18 ],
19 ],
20 'default' => 'center',
21 'toggle' => true,
22 ]
23);

Typografie über Gruppensteuerelement

Das Typografie-Gruppensteuerelement bietet den vollständigen Satz: Schriftart, Größe, Buchstabenabstand, Stärke, alles mit responsiven Breakpoints. Weitere Details in der Group_Control_Typography-Dokumentation. Wichtiger Punkt: Scheme_Typography ist seit Elementor 3.x veraltet, verwenden Sie es nicht. Verknüpfen Sie stattdessen mit globalen Stilen oder lassen Sie den scheme-Schlüssel ganz weg.

1use Elementor\Group_Control_Typography;
2
3$this->add_group_control(
4 Group_Control_Typography::get_type(),
5 [
6 'name' => 'content_typography',
7 'label' => __( 'Typography', 'elementor-awesomesauce' ),
8 'selector' => '{{WRAPPER}} .elementor-awesomesauce',
9 'fields_options' => [
10 'letter_spacing' => [
11 'range' => [
12 'min' => 0,
13 'max' => 100,
14 ],
15 ],
16 ],
17 ]
18);

Warum ein Plugin und nicht functions.php

Ein benutzerdefiniertes Widget im Theme funktioniert. Aber nur, bis Sie das Theme wechseln. In einem separaten Plugin erhalten Sie drei wichtige Vorteile:

  • Bedingtes Laden. Elementor ruft widget_scripts nur dann auf, wenn das Widget tatsächlich auf der Seite angezeigt wird, nicht auf der gesamten Website. Bei Projekten mit Dutzenden von Widgets ist dies eine spürbare Einsparung bei HTTP-Anfragen.
  • Isolation. PHP-Logik, CSS und JS vermischen sich nicht mit dem Theme. Einen Fehler zu finden und zu beheben, dauert Minuten, nicht Stunden des Durchsuchens von 2000 Zeilen in functions.php.
  • Portabilität. Aktivieren Sie das Plugin auf einer anderen Website, das Widget funktioniert. Kein Kopieren und Einfügen und manuelles Anpassen von Pfaden.
Entwickler an einem Schreibtisch mit zwei Monitoren

Video: Live-Demonstration vom leeren Plugin zum funktionierenden Widget

Theorie ist gut, aber Code in Aktion zu sehen, ist schneller. In dieser 30-minütigen Anleitung geht der Autor den gesamten Prozess durch: von einem leeren Ordner zu einem Widget mit Steuerelementen und Ausgabe.

⁉️🤔 Häufig gestellte Fragen

Warum erscheint das Widget nicht im Elementor-Panel?

Prüfen Sie zuerst den Registrierungs-Hook. Seit Elementor 3.5 wird elementor/widgets/register verwendet (nicht elementor/widgets/widgets_registered, das ist veraltet). Zweitens: Die Methode register_controls() muss ohne den Präfix _ auskommen. Drittens: get_categories() muss ein Array mit einer existierenden Kategorie zurückgeben, 'general' funktioniert immer. Viertens: Leeren Sie den WordPress-Cache nach der Aktivierung des Plugins.

Was ist der Unterschied zwischen print_render_attribute_string() und direktem echo in render()?

print_render_attribute_string() wendet automatisch Attributfilter an, einschließlich Inline-Editing und Elementor-Datenattribute. Direktes echo liefert diese Daten nicht, Inline-Editing wird schlicht nicht aktiviert. Für die Ausgabe von Einstellungswerten nutzen Sie print_unescaped_setting(), das Escaping innerhalb von Controls korrekt handhabt.

Kommt man ohne eine separate JS-Datei aus?

Ja, wenn das Widget ausschließlich PHP-Markup ohne Interaktivität rendert. Sobald jedoch Slider, Animationen, AJAX-Loading oder irgendeine clientseitige Dynamik ins Spiel kommen, ist JS zwingend erforderlich. Selbst ein minimaler Handler (wie im obigen Beispiel) schafft einen Einstiegspunkt für künftige Logik, ohne dass die Registrierung später umgeschrieben werden muss.

Ist ein Namespace im Plugin zwingend erforderlich?

Formal nicht. Ohne Namespace riskieren Sie jedoch Klassen-Namenskonflikte mit einem anderen Plugin oder Theme, Bezeichnungen wie Plugin oder Widget sind alles andere als eindeutig. Für den Produktiveinsatz ist er Pflicht. Das Präfix ElementorAwesomesauceWidgets garantiert praktisch Eindeutigkeit.

Wie aktualisiert man ein altes Widget, das nach veralteten Anleitungen geschrieben wurde?

Der Migrationsplan ist im offiziellen Elementor-Leitfaden zu veralteten Funktionen beschrieben: (1) Benennen Sie Methoden mit dem Präfix _ um, _register_controls()register_controls(), _content_template()content_template(); (2) ersetzen Sie den Hook durch elementor/widgets/register und die Registrierungsmethode durch $manager->register(); (3) ersetzen Sie fa fa-*-Icons durch eicon-*, entfernen Sie Scheme_Typography. Erhöhen Sie nach den Änderungen MINIMUM_ELEMENTOR_VERSION auf den aktuellen Stand und testen Sie das Widget auf drei Ebenen: Editor-Panel, Live-Vorschau, Frontend.

Was tun mit alten Widgets: Migrationsplan zur aktuellen API

Falls Sie bereits benutzerdefinierte Widgets nach Anleitungen von 2019-2021 geschrieben haben, keine Panik. Elementor wahrt die Abwärtskompatibilität mit Deprecation-Handlern über bis zu 8 Hauptversionen hinweg. Besser ist es jedoch, den Code jetzt zu aktualisieren, bevor aus Deprecation-Hinweisen fatale Fehler werden.

Migrations-Checkliste:

  • **Methoden ohne **_: Überall, wo Sie _register_controls und _content_template sehen, entfernen Sie den Präfix.
  • Registrierungs-Hook: elementor/widgets/widgets_registeredelementor/widgets/register.
  • Registrierungsmethode: $manager->register_widget_type()$manager->register().
  • Icons: fa fa-*eicon-* (Elementor-eigener Satz) oder dashicons-* (WordPress-Satz).
  • Typografie: Scheme_Typography → entweder globale Stile über 'global' => [...] oder direkte Werte ohne den scheme-Schlüssel.

Testen Sie das Widget nach den Änderungen auf drei Ebenen: Lässt sich das Control-Panel im Editor öffnen, funktioniert die Live-Vorschau (Backbone-Template), rendert das Frontend fehlerfrei. Und denken Sie an den Probelauf vor dem Deployment in die Produktion: Elementor überspringt fehlerhafte Controls stillschweigend, ohne die gesamte Seite zum Absturz zu bringen, daher ist eine visuelle Überprüfung zwingend erforderlich.

Alter Code in functions.php oder Plugin von Grund auf: Was ist zu wählen?

Wenn Sie ein neues Projekt starten, ausschließlich ein Plugin. Es gibt keine Argumente mehr „für" functions.php: Selbst für ein Micro-Widget mit 20 Zeilen zahlt sich die Plugin-Struktur beim ersten Theme-Update aus.

Falls Sie bereits funktionierenden Code im Theme haben, extrahieren Sie ihn beim nächsten Refactoring in ein Plugin. Der Ablauf ist einfach: Erstellen Sie einen Ordner und die Haupt-Plugin-Datei gemäß der obigen Struktur, verschieben Sie die Widget-Klasse nach widgets/, richten Sie die Registrierung über elementor/widgets/register ein und testen Sie auf einer Staging-Umgebung. In der Praxis dauert das für ein typisches Widget 15-20 Minuten.

Beginnen Sie mit der Basisklasse aus diesem Leitfaden, kopieren Sie die Hauptdatei und plugin.php als Gerüst, ersetzen Sie den Widget-Namen durch Ihren eigenen. Und wenn Fragen auftauchen, konsultieren Sie den Widgets-Bereich auf developers.elementor.com: Dort werden das Rendern von Medien, die Arbeit mit Repeater-Feldern und die Optimierung der Ausgabe behandelt.