Jak dodać niestandardowy widget do Elementor

dowiedz się

Chcesz rozbudować kreator stron o własne funkcje i mieć pełną kontrolę nad tym, jak wyglądają i działają elementy na Twojej witrynie? Dodanie własnego, elastycznego komponentu do Elementor to świetna droga: zachowujesz wygodę edycji wizualnej, a jednocześnie możesz wdrożyć dowolną logikę, integracje i style. Poniżej znajdziesz szczegółową instrukcję krok po kroku – od stworzenia minimalnej wtyczki w WordPress, przez rejestrację klasy widgetu, aż po kontrolki, renderowanie i dobre praktyki.

Przygotowanie środowiska i szkielet wtyczki

Wymagania i wersje

Na początek upewnij się, że masz aktualne środowisko:

  • WordPress 6.x lub nowszy oraz aktualny Elementor (wersja zgodna z Twoją instalacją). Aktualizacje często zmieniają API, więc sprawdź dziennik zmian.
  • Minimalna wersja PHP zgodna z Elementor (zwykle 7.4+). Dla długowieczności projektu celuj w nowsze wydania, np. 8.1+.
  • Dostęp do plików WP (FTP/SSH lub lokalnie), edytor kodu oraz włączony tryb debugowania w środowisku deweloperskim (WP_DEBUG true).

Struktura plików wtyczki

Utwórz katalog wtyczki: wp-content/plugins/acme-elementor-widgets. W środku przygotuj bazową strukturę:

  • acme-elementor-widgets.php – plik główny wtyczki (nagłówek, inicjalizacja).
  • includes/ – logika rejestracji, klasy wspólne, helpery.
  • widgets/ – klasy konkretnych widgetów (np. class-acme-sample-widget.php).
  • assets/css/ – style front-end i panelu.
  • assets/js/ – skrypty front-end i do edytora.
  • languages/ – pliki tłumaczeń .po/.mo.
  • readme.txt – opis (przydatny do repozytorium lub dokumentacji zespołowej).

Plik główny i inicjalizacja

W pliku acme-elementor-widgets.php dodaj podstawowy nagłówek i kontrolę bezpieczeństwa:

<?php
/**
* Plugin Name: ACME Elementor Widgets
* Description: Zestaw niestandardowych komponentów dla Elementor.
* Version: 1.0.0
* Author: ACME
* Text Domain: acme-elementor-widgets
*/
if ( ! defined( 'ABSPATH’ ) ) exit;
define( 'ACME_EW_PATH’, plugin_dir_path( __FILE__ ) );
define( 'ACME_EW_URL’, plugin_dir_url( __FILE__ ) );
require_once ACME_EW_PATH . 'includes/plugin.php’;

W includes/plugin.php przygotuj klasę bootstrapu, która zarejestruje Twoje komponenty wtedy, gdy Elementor będzie już aktywny. Dodaj sprawdzenie, czy plugin Elementor jest włączony i dostępny (np. poprzez function_exists lub klasę \Elementor\Plugin).

Ładowanie klas i porządek w kodzie

Jeśli planujesz większy zestaw komponentów, rozważ autoloader (PSR-4) z composer.json. Pomoże to utrzymać porządek i uniknąć ręcznego require’owania wielu plików. Dodatkowo używaj przestrzeni nazw, aby izolować swój kod. W praktyce uporządkuje to aliasy i uniknie kolizji z innymi dodatkami – to dobry moment, by zaplanować strukturę pakietu i ułożyć pliki w logiczne moduły, zamiast trzymać wszystko w jednym pliku. W dalszej części pokażemy rejestrację klas i najważniejsze punkty API.

Rejestrowanie i ładowanie własnego widgetu

Integracja z API: hooki i kolejność zdarzeń

Elementor udostępnia punkty zaczepienia do rejestracji widżetów. Najczęściej używa się akcji elementor/widgets/register (nowsze API) lub elementor/widgets/widgets_registered (starsze). Przykład w includes/plugin.php:

<?php
namespace ACME\ElementorWidgets;
if ( ! defined( 'ABSPATH’ ) ) exit;
class Plugin {
public function __construct() {
add_action( 'plugins_loaded’, [ $this, 'init’ ] );
}
public function init() {
if ( ! did_action( 'elementor/loaded’ ) ) return; // Elementor nieaktywny
add_action( 'elementor/widgets/register’, [ $this, 'register_widgets’ ] );
add_action( 'elementor/frontend/after_enqueue_styles’, [ $this, 'enqueue_assets’ ] );
add_action( 'elementor/editor/after_enqueue_styles’, [ $this, 'enqueue_editor_assets’ ] );
load_plugin_textdomain( 'acme-elementor-widgets’, false, dirname( plugin_basename( __FILE__ ) ) . '/../languages’ );
}
public function register_widgets( $widgets_manager ) {
require_once ACME_EW_PATH . 'widgets/class-acme-sample-widget.php’;
$widgets_manager->register( new Widgets\ACME_Sample_Widget() );
}
public function enqueue_assets() {
wp_register_style( 'acme-ew’, ACME_EW_URL . 'assets/css/front.css’, [], '1.0.0′ );
wp_enqueue_style( 'acme-ew’ );
wp_register_script( 'acme-ew’, ACME_EW_URL . 'assets/js/front.js’, [ 'jquery’ ], '1.0.0′, true );
wp_enqueue_script( 'acme-ew’ );
}
public function enqueue_editor_assets() {
wp_register_style( 'acme-ew-editor’, ACME_EW_URL . 'assets/css/editor.css’, [], '1.0.0′ );
wp_enqueue_style( 'acme-ew-editor’ );
}
}
new Plugin();

Minimalna klasa widgetu

W widgets/class-acme-sample-widget.php dodaj klasę rozszerzającą \Elementor\Widget_Base. To serce komponentu – nazwa, tytuł, kategorie, kontrolki i metoda render() decydują o zachowaniu widżetu.

<?php
namespace ACME\ElementorWidgets\Widgets;
use Elementor\Widget_Base;
use Elementor\Controls_Manager;
if ( ! defined( 'ABSPATH’ ) ) exit;
class ACME_Sample_Widget extends Widget_Base {
public function get_name() { return 'acme-sample’; }
public function get_title() { return esc_html__( 'ACME Przykład’, 'acme-elementor-widgets’ ); }
public function get_icon() { return 'eicon-code’; }
public function get_categories() { return [ 'basic’ ]; }
public function get_keywords() { return [ 'acme’, 'sample’, 'custom’ ]; }
protected function register_controls() {
$this->start_controls_section( 'section_content’, [ 'label’ => esc_html__( 'Treść’, 'acme-elementor-widgets’ ) ] );
$this->add_control( 'title’, [
'label’ => esc_html__( 'Tytuł’, 'acme-elementor-widgets’ ),
'type’ => Controls_Manager::TEXT,
'default’ => esc_html__( 'Witaj, świecie!’, 'acme-elementor-widgets’ ),
'label_block’ => true,
] );
$this->add_control( 'link’, [
'label’ => esc_html__( 'Link’, 'acme-elementor-widgets’ ),
'type’ => Controls_Manager::URL,
'placeholder’ => 'https://’,
'options’ => [ 'is_external’, 'nofollow’ ],
] );
$this->end_controls_section();
}
protected function render() {
$s = $this->get_settings_for_display();
$title = ! empty( $s[’title’] ) ? $s[’title’] : ”;
$url = isset( $s[’link’][’url’] ) ? esc_url( $s[’link’][’url’] ) : ”;
echo '<div class=”acme-sample”>’;
if ( $url ) {
$target = ! empty( $s[’link’][’is_external’] ) ? ’ target=”_blank” rel=”noopener”’ : ”;
echo '<a href=”’ . $url . '”’ . $target . ’>’ . esc_html( $title ) . '</a>’;
} else {
echo '<span>’ . esc_html( $title ) . '</span>’;
}
echo '</div>’;
}
}

Kategorie, ikony i porządkowanie

Domyślnie użyliśmy kategorii basic. Własną kategorię dodasz przez filtr elementor/elements/categories_registered, co ułatwi grupowanie kilku komponentów Twojej wtyczki. Ikony korzystają z zestawu Elementor Icons – dobierz taką, która wskazuje funkcję widżetu (np. eicon-posts, eicon-image). Dobre kategorie i spójne nazewnictwo znacząco podnoszą ergonomię pracy edytora.

Ładowanie stylów i skryptów (front i edytor)

Skrypty i style najlepiej rejestrować globalnie, a ładować tylko wtedy, gdy komponent jest użyty. Elementor wspiera metodę get_style_depends() / get_script_depends() w klasie widżetu – zwróć tam nazwy zarejestrowanych zasobów, a Elementor sam załaduje je w odpowiednim momencie. Pamiętaj, aby nie duplikować paczek JS już obecnych w motywie i pluginach; w przeciwnym razie zwiększysz rozmiar strony i ryzyko konfliktów. Do wstrzykiwania zasobów używaj mechanizmu enqueue WordPressa, dbając o wersjonowanie i zależności (deps).

Kontrolki, logika i renderowanie

Fundamenty konfiguracji: kontrolki treści

Kontrolki to interfejs, dzięki któremu redaktorzy ustawiają zawartość Twojego komponentu. Najczęściej używane typy: TEXT, TEXTAREA, WYSIWYG, MEDIA, URL, SELECT, SWITCHER, NUMBER, SLIDER, COLOR, DIMENSIONS, CHOOSE. Przykłady praktyczne:

  • MEDIA – aby pobrać obraz i renderować go ze srcset/sizes dla responsywności.
  • URL – do zarządzania linkiem z opcjami nofollow i target blank.
  • COLOR – do wyboru koloru tytułu bez konieczności pisania CSS przez końcowego użytkownika.

Używaj label_block true dla dłuższych pól, placeholderów, oraz validacji po stronie PHP, jeśli dane wpływają na zapytania lub integracje z zewnętrznymi usługami.

Repeater, warunki i Dynamic Tags

Repeater pozwala tworzyć powtarzalne grupy pól (np. lista linków, slajdy, FAQ). Pamiętaj o ergonomii: pola kluczowe ustaw na górze, a mniej używane schowaj w sekcji Advanced. Zależne wyświetlanie (conditions) ułatwia panel – pokazujesz tylko to, co ma sens po włączeniu danej opcji. Dynamic Tags integrują widget z treściami WP (tytuł, ACF, pola niestandardowe) bez kodowania – wystarczy oznaczyć kontrolkę jako obsługującą dynamiczne źródła.

Frontend i logika: metoda render()

Metoda render() jest wywoływana w przeglądarce odwiedzającego stronę. Wyprowadzaj minimalny, semantyczny HTML: dopasowane headingi, listy, przyciski o zdefiniowanych rolach. Zawsze stosuj esc_html, esc_attr i esc_url, a gdy wstawiasz fragmenty HTML od użytkownika – filtruj przez wp_kses z białą listą dozwolonych tagów. Uważaj na logikę warunkową: nie renderuj pustych wrapperów, jeśli dane nie są wprowadzone. Zadbaj o lazy-loading obrazów i przemyślany porządek znaczników dla dostępności.

Inline editing i tryb edytora

Elementor pozwala na inline editing (edytowanie treści bezpośrednio w podglądzie). Aby to aktywować dla pola tekstowego, zarejestruj je jako editable w renderowaniu (np. dodając atrybut data-elementor-setting-key). Dodatkowo, w metodzie _content_template (w starszym API) lub w JS edytora, możesz przygotować dynamiczny podgląd bez przeładowywania. Ogranicz jednak złożoność szablonów w edytorze – rozbieżności między edytorem a frontem to częsta przyczyna błędów wizualnych.

Stylowanie, skrypty i wydajność

CSS: architektura i selektory

Projektuj style komponentu tak, by nie przeciekały poza jego kontener. Używaj selektora :host w kontekście edytora i prefiksów klas (np. .acme-card). Rozważ BEM, by utrzymać spójność. Jeżeli oferujesz kontrolki stylu (np. kolor, marginesy, typografia), podepnij je przez Controls_Manager::COLOR/SLIDER i mapuj na CSS za pomocą dynamicznych reguł (selectors w definicji kontrolki). Unikaj !important – jeśli musisz, to znaczy, że selektor jest zbyt słaby lub kolidujesz z motywem.

JavaScript: interakcje i zależności

Skrypty ładuj tylko, gdy komponent występuje na stronie: zwracaj nazwy uchwytów w get_script_depends(). Jeśli korzystasz z jQuery, zadeklaruj je jako zależność; dla nowoczesnych projektów rozważ czyste ES6 i kompilację przez narzędzia buildujące. Jeżeli potrzebujesz danych z WordPressa, użyj wp_localize_script do przekazania bezpiecznych endpointów i nonce. Listenerów nie przywiązuj globalnie do window – nasłuchuj w obrębie root elementu widżetu, aby uniknąć wycieków i konfliktów.

Assety, obrazki i critical CSS

Jeśli komponent jest powtarzany wiele razy (np. karty produktów), staraj się unikać duplikowania inline CSS. Zamiast tego kieruj się do pliku assets/css/front.css z klasami wielokrotnego użytku. Dla obrazów korzystaj z responsywnych atrybutów (wp_get_attachment_image) i lazy-loading. Jeżeli fragment stylu jest kluczowy dla pierwszego malowania, możesz wstrzyknąć niewielkie critical CSS, ale rób to selektywnie, by nie rozrastać HTML.

Optymalizacja: analiza, cache i metryki

Podchodź systemowo do optymalizacji: mierz TTFB, LCP, CLS, INP po wdrożeniu widżetu. Kluczowe jest cachowanie wyników ciężkich zapytań (transienty, obiekty w pamięci), paginacja przy listach i debouncing zdarzeń w JS. Jeżeli komponent komunikuje się z API, wprowadź backoff i budżet żądań, a dane cache’uj krótko w lokalnym storage lub przez REST proxy na serwerze. Zadbaj o semantyczny, krótki DOM – każdy węzeł to koszt. To wszystko realnie wpływa na wydajność.

Bezpieczeństwo, tłumaczenia, testy i publikacja

Walidacja i kontekst

Wprowadzane przez użytkownika dane traktuj jako nieufne. Sanityzuj je już na etapie zapisu (sanitize_text_field, esc_url_raw), a przed wyjściem używaj odpowiednich esc_* zależnie od kontekstu. Dla HTML dopuszczonego w treści używaj wp_kses z jasno określoną białą listą tagów i atrybutów. Gdy tworzysz akcje AJAX lub REST, weryfikuj nonce, uprawnienia (current_user_can), a parametry rzutuj do typów prymitywnych. Dodatkowo rozważ rate limiting i krótkie TTL cache po stronie serwera dla endpointów, które mogą być celem nadużyć.

Funkcje esc_*, XSS i SSRF

Najczęstsze błędy to: mieszanie esc_html i esc_attr, brak esc_url dla href/src, oraz echo surowych danych w atrybutach. Nigdy nie pobieraj zewnętrznych zasobów bez walidacji hosta i schematu; w razie potrzeby użyj wp_safe_remote_get z ograniczeniami i sprawdzaj Content-Type. Pamiętaj, że edytor może wstrzyknąć znak nowej linii do wartości – w atrybutach zawsze esc_attr. Unikaj eval, Function i new Function w JS – to prosta droga do XSS.

Tłumaczenia i dostępność

Każdy tekst opakuj w funkcje tłumaczeń: __, _e, esc_html__, esc_attr__. Używaj domeny tekstowej z nagłówka wtyczki. Zapewnij alternatywne teksty dla obrazów i role/aria-labels dla elementów interaktywnych. Focus state musi być widoczny; testuj klawiaturą i czytnikiem ekranu. Kontrasty i rozmiary kliknięć (min. 44×44 px) wpływają nie tylko na zgodność, ale i komfort użytkowników.

Debugowanie i testy

Włącz WP_DEBUG_LOG w środowisku deweloperskim. Dodaj testy jednostkowe (PHPUnit) dla metod, które przetwarzają dane, oraz testy e2e dla najważniejszych scenariuszy (np. poprzez Playwright w trybie headless). Monitoruj logi serwera, a w przeglądarce używaj Performance Panel do identyfikacji wolnych skryptów. Testuj widget na motywie bazowym (np. Hello) oraz na popularnych motywach, by wyłapać kolizje stylów.

Wersjonowanie, kompatybilność i publikacja

W plikach CSS/JS wersjonuj assety (np. numer wersji w enqueue), aby wymusić odświeżenie cache po aktualizacji. W readme dokumentuj wymagania i listę zmian. Przed publikacją sprawdź zgodność licencyjną zasobów (ikony, czcionki). Jeśli planujesz dystrybucję szerszą niż jedna witryna, rozważ system aktualizacji (GitHub Updater, własny endpoint) lub publikację w repozytorium. Nie zapomnij o WSOD protection: warunkowo ładuj funkcje powiązane z Elementor tylko wtedy, gdy jest on aktywny – w przeciwnym razie wyświetl jasny komunikat w panelu administratora.

Rozszerzenia: query, AJAX, integracje i dobre praktyki architektoniczne

Zapytania do treści i kontrolki Query

Jeżeli Twój komponent ma listować posty, produkty czy taksonomie, zbuduj kontrolki: wybór typu treści, filtr kategorii, liczba elementów, kolejność. Zapytania realizuj przez WP_Query z parametrami wynikającymi z kontrolek. Zabezpiecz się przed nadmiernym obciążeniem: limit per-page, offset tylko gdy konieczny, a przy paginacji używaj stronicowania opartego o query vars zamiast ręcznie budowanych URL. Buforuj wyniki (transienty) i czyść je na aktualizację treści.

AJAX/REST: szybkie interakcje

Dla dynamicznych elementów (filtrowanie, ładowanie „więcej”) opracuj endpoint REST lub akcję AJAX. Dodaj nonce i sprawdzaj capabilities. Wynik renderuj częściowo po stronie serwera (np. gotowy HTML elementów listy) – to upraszcza frontend i ułatwia spójność stylów. Jeśli renderujesz po stronie klienta, przesyłaj tylko dane, a HTML składaj w JS szablonach. W obu przypadkach rozważ fallback bez JS (progressive enhancement).

Integracje z zewnętrznymi usługami

Przy wpinaniu usług (mapy, płatności, wyszukiwarki) izoluj konfigurację w jednej sekcji kontrolek i pozwól użytkownikowi wprowadzić klucze API. Ogranicz liczbę żądań (debounce, throttle) i cache’uj odpowiedzi. W UI sygnalizuj błędy i time-outy, aby edytor mógł zareagować (np. wyświetlić placeholder). Na produkcji włącz obserwację błędów JS (window.onerror) z raportowaniem do logów – integracje są najbardziej podatne na zmiany po stronie dostawcy.

Architektura: modułowość, testowalność i spójność

Każdy widżet traktuj jako moduł: osobna klasa, dedykowane pliki CSS/JS, testy i dokumentacja. Dzielenie na warstwy (kontrolki, logika danych, prezentacja) ułatwia utrzymanie i umożliwia ponowne wykorzystanie fragmentów. Wspólne helpery (np. sanetyzacja, generatory atrybutów ARIA, budowa klas CSS) przenieś do katalogu includes. Dbaj o spójne nazwy i prefiksy – unikniesz konfliktów z innymi dodatkami i motywami.

Checklist przed wdrożeniem

  • Brak błędów w konsoli, czyste logi PHP i serwera.
  • Poprawne działanie w edytorze i na froncie, zgodność wizualna.
  • Sanityzacja i escaping wszystkich danych użytkownika.
  • Minimalna liczba zasobów, brak duplikacji bibliotek.
  • Responsywność i dostępność (kontrasty, fokus, aria, kolejność TAB).
  • Testy na kilku motywach i w trybie bez cache oraz z cache CDN/WP.
  • Opis kontrolek i krótkie tooltipy objaśniające ich działanie.

Jeżeli wdrożysz powyższe praktyki, Twój niestandardowy komponent będzie działał stabilnie, bezpiecznie i wygodnie dla redaktorów. Budując kolejne elementy, konsekwentnie powielaj schemat: zaplanowane kontrolki, przejrzyste hooki, poprawne renderowanie, oszczędne ładowanie assetów przez enqueue i bezkompromisowe bezpieczeństwo. Dzięki temu Twoje widżety dla Elementor będą skalowalne i przyjazne w utrzymaniu – zarówno w pojedynczych projektach, jak i w większych bibliotekach komponentów dla WordPress. Wspieraj to dobrą jakością kodu w PHP, przemyślanym API kontrolek i konsekwentnym wykorzystaniem metody render(), a użytkownicy zyskają intuicyjne narzędzia, które nie poświęcają wydajność kosztem wyglądu czy funkcjonalności.

< Powrót

Zapisz się do newslettera


Zadzwoń Napisz