Skip to content

Alt om WordPress, webutvikling — og mer til

🛠️ Tilpassede Elementor-widgets: full syklus fra plugin til kontroller

🛠️ 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).

Koderedigeringsprogrammer på en dataskjerm

💡 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, definer register_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:

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

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
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();

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
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();

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
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}

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.

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);

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_scripts bare 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.
Utvikler ved et skrivebord med to skjermer

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 (ikke elementor/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. Direkte echo gir ikke disse dataene, og innebygd redigering vil rett og slett ikke aktiveres. For å skrive ut innstillingsverdier, bruk print_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 Plugin eller Widget er langt fra unike. For produksjon er det obligatorisk. Prefikset ElementorAwesomesauceWidgets garanterer 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 med elementor/widgets/register, og registreringsmetoden med $manager->register(); (3) erstatt fa fa-*-ikoner med eicon-*, fjern Scheme_Typography. Etter redigering, øk MINIMUM_ELEMENTOR_VERSION til 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_controls og _content_template, fjern prefikset.
  • Registreringshook: elementor/widgets/widgets_registeredelementor/widgets/register.
  • Registreringsmetode: $manager->register_widget_type()$manager->register().
  • Ikoner: fa fa-*eicon-* (Elementors eget sett) eller dashicons-* (WordPress-settet).
  • Typografi: Scheme_Typography → enten globale stiler gjennom 'global' => [...], eller direkte verdier uten scheme-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.