
🛠️ Anpassade Elementor-widgets: hel cykel från plugin till kontroller
Temauppdateringen slog sönder det anpassade blocket som visade kampanjer på startsidan. Låter det bekant? Koden ligger i functions.php, stilar är utsmetade över style.css, och JS-hanteraren är fastspikad i footer.php. Byt tema eller gör en större Elementor-uppdatering, så måste du pussla ihop allt detta från grunden.
Problemet är inte att "koden är dålig". Problemet är att den ligger på fel ställe. Anpassade modifieringar som trycks in i temat är permanent knutna till det: att överföra till en annan sajt är omöjligt, felsökning av konflikter är plågsamt, och villkorlig inläsning av assets existerar inte.
Rätt tillvägagångssätt är att paketera den anpassade widgeten som ett separat plugin. Det är precis vad den officiella Elementor-dokumentationen rekommenderar för alla icke-triviala anpassningar. Kodisolering, temaoberoende, korrekt versionshantering och JS-inläsning endast på sidor med widgeten. I den här guiden, hela cykeln: från plugin-struktur till en live-widget med inline-redigering, på det aktuella Elementor-API:et (3.x/4.x).

💡 Snabb översikt:
- Registrera ett separat plugin för widgeten, isolera koden från temat en gång för alla.
- Bygg huvudklassen med kompatibilitetskontroller: Elementor aktivt, version inte lägre än 3.5, PHP från 7.4.
- Skapa widgetklassen: ärv
Widget_Base, definieraregister_controls()och renderingsmetoden. - Koppla upp inline-textredigering via
add_inline_editing_attributes()och en Backbone-mall. - Lägg till anpassade fält: bildväljare från mediebiblioteket, dropdowns, knappgrupper, typografi.
- Haka fast JavaScript-hanteraren på
elementor/frontend/init, ingångspunkten för all klientlogik.
Plugin-struktur och huvudklass
Vi skapar pluginet Elementor Awesomesauce. Minimal filstruktur:
1 elementor-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
Huvudplugin-fil, standard WordPress-header plus en laddarklass med kompatibilitetskontroller. Detta är exakt den struktur som beskrivs i Elementors guide för första tillägg. Koden nedan fungerar i Elementor 3.5+ och Elementor 4.x: metoder utan det föråldrade _-prefixet, konstanter för moderna PHP-versioner.
Lägg till denna kod i elementor-awesomesauce.php. Filen ska ligga i /wp-content/plugins/elementor-awesomesauce/. Gör en fullständig säkerhetskopia av sajten innan aktivering.
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 11 if ( ! defined( 'ABSPATH' ) ) exit; 12 13 final 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 86 new Elementor_Awesomesauce();
Vad som är viktigt här. Konstanten MINIMUM_ELEMENTOR_VERSION, 3.5.0. Från och med denna version började nya konventioner för hook- och metodnamn utan _-prefixet att gälla. För Elementor 4.x (aktuell version i juni 2026) fungerar samma kod utan ändringar, widget-API:et i 4.x bröts inte. PHP, minimum 7.4, men i praktiken är 8.x redan de facto-standard. Klassen deklareras final: detta är ingångspunkten, inget behov av att ärva från den.
Plugin-klass: singleton och widget-registrering
Fil plugin.php, en singleton som laddar JS-assets och registrerar widgeten via hooken elementor/widgets/register. Detta är den huvudsakliga förändringen jämfört med gamla guider: hooken elementor/widgets/widgets_registered förklarades föråldrad från version 3.5.0, metoden register_widget_type() ersattes med register().
1 <?php 2 namespace ElementorAwesomesauce; 3 4 use Elementor\Plugin as ElementorPlugin; 5 6 class 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 42 Plugin::instance();
Notera: register_widgets tar en $widgets_manager-parameter. I den nya hooken skickas managern in direkt, inte hämtas via ElementorPlugin::instance()->widgets_manager. Renare och utan den extra importen.
Widget-klass: arv, kontroller och rendering
Fil widgets/awesomesauce.php, hjärtat i pluginet. Ärv Widget_Base, definiera namn, titel, ikon och kategori. Metoden register_controls() lägger till tre textfält: enkelrad, flerrad och WYSIWYG. Metoden render() matar ut markup på frontend, och content_template() definierar Backbone-mallen för live-förhandsvisning i editorn.
1 <?php 2 namespace ElementorAwesomesauceWidgets; 3 4 use Elementor\Widget_Base; 5 use Elementor\Controls_Manager; 6 7 if ( ! defined( 'ABSPATH' ) ) exit; 8 9 class 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 }
Vad som har ändrats jämfört med föråldrade guider. Metoderna register_controls() och content_template() skrivs utan prefixet _, detta är en ändring sedan Elementor 3.1. För utdata i render(), använd print_render_attribute_string() och print_unescaped_setting() istället för direkt echo, ett modernt tillvägagångssätt som rekommenderas sedan Elementor 3.x och fungerar i 4.x. Ikon ersatt från fa fa-pencil (Font Awesome 4, borttagen ur kärnan) till eicon-pencil från Elementors egna ikonuppsättning.
Anpassade fält: media, select, typografi
Textfält begränsar inte möjligheterna. Vi går igenom fyra kontrolltyper som hanterar de flesta verkliga scenarier.
Mediafält
Controls_Manager::MEDIA lägger till standardbildval från WordPress mediebibliotek. Metoden Utils::get_placeholder_image_src() fungerar i aktuella versioner och tillhandahåller en grå platshållare om ingen bild har valts.
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 );
Rullgardinslista
Controls_Manager::SELECT, val från fördefinierade värden. Nedan är ett exempel för HTML-rubriknivå:
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 );
Knappgrupp
Controls_Manager::CHOOSE visar en rad med ikoner för visuellt val. Ikoner, endast från eicon-*-uppsättningen, inte 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 );
Typografi via gruppkontroll
Gruppkontrollen för typografi tillhandahåller hela uppsättningen: teckensnitt, storlek, teckenavstånd, vikt, allt med responsiva brytpunkter. Mer information i dokumentationen för Group_Control_Typography. Viktig punkt: Scheme_Typography har varit föråldrat sedan Elementor 3.x, använd det inte. Länka istället till globala stilar eller utelämna nyckeln scheme helt.
1 use 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 );
Varför ett plugin och inte functions.php
En anpassad widget i temat fungerar. Men bara tills du byter tema. I ett separat plugin får du tre viktiga fördelar:
- Villkorlig inläsning. Elementor anropar
widget_scriptsendast när widgeten faktiskt visas på sidan, inte över hela webbplatsen. För projekt med dussintals widgets är detta en märkbar besparing i HTTP-anrop. - Isolering. PHP-logik, CSS och JS blandas inte med temat. Att hitta och åtgärda en bugg tar minuter, inte timmar av grävande genom 2000 rader i
functions.php. - Portabilitet. Aktivera pluginet på en annan webbplats, widgeten fungerar. Ingen kopiering och manuell redigering av sökvägar.

Video: livedemonstration från tomt plugin till fungerande widget
Teori är bra, men att se kod i aktion går snabbare. I denna 30-minutersguide går författaren igenom hela processen: från en tom mapp till en widget med kontroller och rendering.
⁉️🤔 Vanliga frågor
Varför syns inte widgeten i Elementor-panelen?
Kontrollera först registreringshooken. Sedan Elementor 3.5 används
elementor/widgets/register(inteelementor/widgets/widgets_registered, den är föråldrad). För det andra: metodenregister_controls()måste vara utan prefixet_. För det tredje:get_categories()måste returnera en array med en befintlig kategori,'general'fungerar alltid. För det fjärde: rensa WordPress cache efter att du har aktiverat pluginet.
Vad är skillnaden mellan print_render_attribute_string() och direkt echo i render()?
print_render_attribute_string()tillämpar automatiskt attributfilter, inklusive inline-redigering och Elementors dataattribut. Direktechoger inte denna data, inline-redigering aktiveras helt enkelt inte. För att skriva ut inställningsvärden, användprint_unescaped_setting(), den hanterar escaping korrekt inuti kontroller.
Klarar man sig utan en separat JS-fil?
Ja, om widgeten enbart renderar PHP-uppmärkning utan interaktivitet. Men så fort reglage, animationer, AJAX-laddning eller någon klientdynamik förekommer är JS obligatoriskt. Även en minimal hanterare (som i exemplet ovan) ger en ingångspunkt för framtida logik, utan att kräva att registreringen skrivs om senare.
Är en namespace obligatorisk i pluginet?
Formellt sett nej. Men utan en namespace riskerar du att få klassnamnskonflikter med ett annat plugin eller tema, namn som
PluginellerWidgetär långt ifrån unika. För produktion, obligatoriskt. PrefixetElementorAwesomesauceWidgetsgaranterar praktiskt taget unikhet.
Hur uppdaterar man en gammal widget skriven enligt föråldrade guider?
Migreringsplanen beskrivs i den officiella Elementor-guiden för föråldrad funktionalitet: (1) byt namn på metoder med prefixet
_,_register_controls()→register_controls(),_content_template()→content_template(); (2) ersätt hooken medelementor/widgets/register, och registreringsmetoden med$manager->register(); (3) ersättfa fa-*-ikoner medeicon-*, ta bortScheme_Typography. Efter redigeringarna, höjMINIMUM_ELEMENTOR_VERSIONtill aktuell och testa widgeten på tre nivåer: redigeringspanel, liveförhandsvisning, frontend.
Vad man gör med gamla widgets: migreringsplan till det aktuella API:et
Om du redan har anpassade widgets skrivna enligt guider från 2019-2021, få inte panik. Elementor upprätthåller bakåtkompatibilitet med hanterare för föråldrad kod i upp till 8 huvudversioner. Men det är bättre att uppdatera koden nu, innan varningar om föråldrad kod förvandlas till fatala fel.
Migreringschecklista:
- **Metoder utan **
_: överallt där du ser_register_controlsoch_content_template, ta bort prefixet. - Registreringshook:
elementor/widgets/widgets_registered→elementor/widgets/register. - Registreringsmetod:
$manager->register_widget_type()→$manager->register(). - Ikoner:
fa fa-*→eicon-*(Elementors egna uppsättning) ellerdashicons-*(WordPress uppsättning). - Typografi:
Scheme_Typography→ antingen globala stilar via'global' => [...], eller direkta värden utan nyckelnscheme.
Efter ändringarna, testa widgeten på tre nivåer: öppnas kontrollpanelen i redigeraren, fungerar liveförhandsvisningen (Backbone-mall), renderas frontenden utan fel. Och kom ihåg torrkörning innan driftsättning till prod: Elementor hoppar tyst över trasiga kontroller utan att krascha hela sidan, så visuell verifiering är obligatorisk.
Gammal kod i functions.php eller plugin från grunden: vad ska man välja
Om du startar ett nytt projekt, endast ett plugin. Det finns inga argument kvar "för" functions.php: även för en mikrowidget på 20 rader lönar sig plugin-strukturen vid första temauppdateringen.
Om du redan har fungerande kod i temat, extrahera den till ett plugin vid nästa refaktorisering. Processen är okomplicerad: skapa en mapp och en huvudplugin-fil enligt strukturen ovan, flytta widget-klassen till widgets/, sätt upp registrering via elementor/widgets/register och testa på staging. I praktiken tar detta 15-20 minuter för en typisk widget.
Börja med basklassen från denna guide, kopiera huvudfilen och plugin.php som ett skelett, ersätt widgetnamnet med ditt eget. Och när frågor uppstår, kolla Widgets-sektionen på developers.elementor.com: den täcker mediarendering, arbete med repeater-fält och optimering av utdata.



