
🛠️ Tilpassede Elementor-widgets: full syklus fra plugin til kontroller
Temaoppdateringen ødela den egendefinerte blokken som viste kampanjer på hjemmesiden. Høres det kjent ut? Koden ligger i functions.php, stilene er spredt utover style.css, og JS-behandleren er spikret til footer.php. Bytt tema eller gjør en større Elementor-oppdatering, så må du sette sammen alt dette fra bunnen av.
Problemet er ikke at «koden er dårlig». Problemet er at den ligger på feil sted. Egendefinerte modifikasjoner som er stappet inn i temaet, er permanent bundet til det: overføring til et annet nettsted er umulig, feilsøking av konflikter er smertefullt, og betinget innlasting av ressurser er ikke-eksisterende.
Den riktige tilnærmingen er å pakke den egendefinerte widgeten som en separat plugin. Det er nøyaktig hva den offisielle Elementor-dokumentasjonen anbefaler for alle ikke-trivielle tilpasninger. Kodeisolering, temauavhengighet, skikkelig versjonering og JS-innlasting kun på sider med widgeten. I denne veiledningen, den komplette syklusen: fra plugin-struktur til en live widget med inline-redigering, på det gjeldende Elementor-API-et (3.x/4.x).

💡 Rask oversikt:
- Registrer en separat plugin for widgeten, isoler kode fra temaet en gang for alle.
- Bygg hovedklassen med kompatibilitetssjekker: Elementor aktiv, versjon ikke lavere enn 3.5, PHP fra 7.4.
- Opprett widget-klassen: utvid
Widget_Base, definerregister_controls()og render-metoden. - Koble til inline tekstredigering gjennom
add_inline_editing_attributes()og en Backbone-mal. - Legg til egendefinerte felt: bildevelger fra mediebiblioteket, nedtrekkslister, knappegrupper, typografi.
- Hekt JavaScript-behandleren på
elementor/frontend/init, inngangspunktet for all klientlogikk.
Plugin-struktur og hovedklasse
Vi skal lage pluginen 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
Hoved-plugin-fil, standard WordPress-header pluss en lasterklasse med kompatibilitetssjekker. Dette er nøyaktig strukturen beskrevet i Elementors første tilleggsveiledning. Koden nedenfor fungerer i Elementor 3.5+ og Elementor 4.x: metoder uten det utdaterte _-prefikset, konstanter for moderne PHP-versjoner.
Legg til denne koden i elementor-awesomesauce.php. Filen skal ligge i /wp-content/plugins/elementor-awesomesauce/. Før du aktiverer, ta en full sikkerhetskopi av nettstedet.
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();
Hva som er viktig her. Konstanten MINIMUM_ELEMENTOR_VERSION, 3.5.0. Fra og med denne versjonen trådte nye konvensjoner for navngiving av hooks og metoder uten _-prefikset i kraft. For Elementor 4.x (gjeldende versjon per juni 2026) fungerer den samme koden uten endringer, widget-API-et i 4.x ble ikke ødelagt. PHP, minimum 7.4, men i praksis er 8.x allerede de facto-standarden. Klassen er deklarert final: dette er inngangspunktet, ikke noe behov for å arve fra den.
Plugin-klasse: singleton og widget-registrering
Fil plugin.php, en singleton som laster JS-ressurser og registrerer widgeten gjennom elementor/widgets/register-hooken. Dette er hovedendringen sammenlignet med gamle veiledninger: elementor/widgets/widgets_registered-hooken ble erklært utdatert fra versjon 3.5.0, metoden register_widget_type() ble erstattet 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();
Merk: register_widgets aksepterer en $widgets_manager-parameter. I den nye hooken sendes manager-objektet direkte, ikke hentet via ElementorPlugin::instance()->widgets_manager. Renere og uten den ekstra importen.
Widget-klasse: arv, kontroller og rendering
Fil widgets/awesomesauce.php, hjertet i pluginen. Utvid Widget_Base, definer navn, tittel, ikon og kategori. Metoden register_controls() legger til tre tekstfelt: enkeltlinje, flerlinje og WYSIWYG. Metoden render() sender ut markup på frontend, og content_template() definerer Backbone-malen for live forhåndsvisning i editoren.
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 }
Hva som er endret i forhold til utdaterte guider. Metodene register_controls() og content_template() skrives uten _-prefiks, dette er en endring siden Elementor 3.1. For utdata i render(), bruk print_render_attribute_string() og print_unescaped_setting() i stedet for direkte echo, en moderne tilnærming anbefalt siden Elementor 3.x og som fungerer i 4.x. Ikon byttet fra fa fa-pencil (Font Awesome 4, fjernet fra kjernen) til eicon-pencil fra Elementors eget sett.
Egendefinerte felt: media, nedtrekkslister, typografi
Tekstfelt begrenser ikke mulighetene. Vi dekker fire kontrolltyper som håndterer de fleste reelle scenarioer.
Mediafelt
Controls_Manager::MEDIA legger til standard bildevalg fra WordPress' mediebibliotek. Metoden Utils::get_placeholder_image_src() fungerer i gjeldende versjoner og gir en grå plassholder hvis ingen bilde er valgt.
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 );
Nedtrekksliste
Controls_Manager::SELECT, valg fra forhåndsdefinerte verdier. Nedenfor er et eksempel for HTML-overskriftstag:
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 );
Knappegruppe
Controls_Manager::CHOOSE viser en rad med ikoner for visuelt valg. Ikoner, kun fra eicon-*-settet, ikke 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 gruppekontroll
Typografi-gruppekontrollen gir hele settet: skrifttype, størrelse, tegnavstand, vekt, alt med responsive brytepunkter. Flere detaljer i dokumentasjonen for Group_Control_Typography. Nøkkelpoeng: Scheme_Typography har vært avviklet siden Elementor 3.x, ikke bruk den. Koble i stedet til globale stiler eller utelat scheme-nøkkelen 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 );
Hvorfor en plugin og ikke functions.php
En egendefinert widget i temaet fungerer. Men bare inntil du bytter tema. I en separat plugin får du tre viktige fordeler:
- Betinget lasting. Elementor kaller
widget_scriptsbare når widgeten faktisk vises på siden, ikke på tvers av hele nettstedet. For prosjekter med dusinvis av widgets er dette en merkbar besparelse i HTTP-forespørsler. - Isolasjon. PHP-logikk, CSS og JS blandes ikke med temaet. Å finne og fikse en feil tar minutter, ikke timer med graving gjennom 2000 linjer i
functions.php. - Portabilitet. Aktiver pluginen på et annet nettsted, så fungerer widgeten. Ingen kopiering og manuell redigering av filstier.

Video: live-demonstrasjon fra tom plugin til fungerende widget
Teori er bra, men å se kode i aksjon går raskere. I denne 30-minutters guiden går forfatteren gjennom hele prosessen: fra en tom mappe til en widget med kontroller og rendering.
⁉️🤔 Ofte stilte spørsmål
Hvorfor vises ikke widgeten i Elementor-panelet?
Sjekk registreringshooken først. Siden Elementor 3.5 brukes
elementor/widgets/register(ikkeelementor/widgets/widgets_registered, den er utdatert). For det andre:register_controls()-metoden må være uten_-prefiks. For det tredje:get_categories()må returnere en array med en eksisterende kategori,'general'fungerer alltid. For det fjerde: tøm WordPress-hurtigbufferen etter at du har aktivert utvidelsen.
Hva er forskjellen på print_render_attribute_string() og direkte echo i render()?
print_render_attribute_string()bruker automatisk attributtfiltre, inkludert innebygd redigering og Elementor-dataattributter. Direkteechogir ikke disse dataene, og innebygd redigering vil rett og slett ikke aktiveres. For å skrive ut innstillingsverdier, brukprint_unescaped_setting(), den håndterer escaping korrekt inne i kontroller.
Kan man klare seg uten en egen JS-fil?
Ja, hvis widgeten kun rendrer PHP-markup uten interaktivitet. Men så snart det dukker opp slider, animasjoner, AJAX-innlasting eller noen form for klientdynamikk, er JS obligatorisk. Selv en minimal handler (som i eksempelet over) gir et inngangspunkt for fremtidig logikk, uten å måtte skrive om registreringen senere.
Er et navnerom obligatorisk i utvidelsen?
Formelt sett nei. Men uten et navnerom risikerer du å få klassenavnkonflikter med en annen utvidelse eller et tema, navn som
PluginellerWidgeter langt fra unike. For produksjon er det obligatorisk. PrefiksetElementorAwesomesauceWidgetsgaranterer praktisk talt unikhet.
Hvordan oppdaterer man en gammel widget skrevet etter utdaterte veiledninger?
Migreringsplanen er beskrevet i den offisielle Elementor-veiledningen for utdaterte funksjoner: (1) endre navn på metoder med
_-prefiks,_register_controls()→register_controls(),_content_template()→content_template(); (2) erstatt hooken medelementor/widgets/register, og registreringsmetoden med$manager->register(); (3) erstattfa fa-*-ikoner medeicon-*, fjernScheme_Typography. Etter redigering, økMINIMUM_ELEMENTOR_VERSIONtil gjeldende versjon og test widgeten på tre nivåer: redigeringspanel, live forhåndsvisning, frontend.
Hva gjør man med gamle widgeter: migreringsplan til gjeldende API
Hvis du allerede har egendefinerte widgeter skrevet etter veiledninger fra 2019-2021, ikke få panikk. Elementor opprettholder bakoverkompatibilitet med utdateringshåndterere i opptil 8 hovedversjoner. Men det er bedre å oppdatere koden nå, før utdateringsvarsler blir til fatale feil.
Sjekkliste for migrering:
- **Metoder uten **
_: overalt hvor du ser_register_controlsog_content_template, fjern prefikset. - Registreringshook:
elementor/widgets/widgets_registered→elementor/widgets/register. - Registreringsmetode:
$manager->register_widget_type()→$manager->register(). - Ikoner:
fa fa-*→eicon-*(Elementors eget sett) ellerdashicons-*(WordPress-settet). - Typografi:
Scheme_Typography→ enten globale stiler gjennom'global' => [...], eller direkte verdier utenscheme-nøkkelen.
Etter endringer, test widgeten på tre nivåer: åpnes kontrollpanelet i redigeringsverktøyet, fungerer live forhåndsvisning (Backbone-mal), rendres frontend uten feil. Og husk tørrkjøring før du deployer til prod: Elementor hopper lydløst over ødelagte kontroller uten å krasje hele siden, så visuell verifisering er obligatorisk.
Gammel kode i functions.php eller utvidelse fra bunnen av: hva bør man velge
Hvis du starter et nytt prosjekt, kun en utvidelse. Det finnes ingen argumenter igjen «for» functions.php: selv for en mikrowidget på 20 linjer lønner utvidelsesstrukturen seg ved første temaoppdatering.
Hvis du allerede har fungerende kode i temaet, trekk den ut i en utvidelse ved neste refaktorering. Prosessen er grei: opprett en mappe og hovedutvidelsesfil i henhold til strukturen over, flytt widgetklassen til widgets/, sett opp registrering gjennom elementor/widgets/register, og test på staging. I praksis tar dette 15-20 minutter for en typisk widget.
Start med basisklassen fra denne veiledningen, kopier hovedfilen og plugin.php som et skjelett, bytt ut widgetnavnet med ditt eget. Og når det dukker opp spørsmål, sjekk Widgets-delen på developers.elementor.com: den dekker mediarendring, arbeid med repeater-felt og optimalisering av output.



