helban.dev

GenerateBlocks Pro i GP Premium: osiem pułapek, których nie ma w dokumentacji

Zbudowałem stronę na GeneratePress Premium i GenerateBlocks Pro, czytając źródła wtyczek zamiast blogów z recenzjami. Osiem rzeczy kosztowało mnie czas. Żadna z nich nie jest bugiem: wszystkie to nieudokumentowane decyzje projektowe i właśnie dlatego są drogie, bo kod robi dokładnie to, co miał robić, tylko nikt nie spisał, co takiego. Każda niżej: objaw, co się naprawdę dzieje, jak to obejść.

Stack: WordPress 7.0.1 · GP Premium 2.5.6 · GenerateBlocks 2.3 + Pro 2.6.1 Metoda: źródła wtyczek + test, który mógł się nie udać Sprawdzone: lipiec 2026
8
pułapek: objaw, przyczyna, obejście
0
z nich to bug wtyczki; to nieudokumentowany design
0
kluczy licencyjnych, żeby płatne funkcje działały
Skąd to się wzięło

Płatne części są opisane najgorzej

GeneratePress i GenerateBlocks mają dobrą darmową wersję i chudy podręcznik do płatnej. Moduł formularzy w GenerateBlocks Pro wyszedł 23 czerwca 2026, więc w chwili pisania ma kilka tygodni, a większość tego, co o nim znajdziesz, to powtórzona zapowiedź producenta. Wszystko niżej wyszło ze źródeł wtyczek i z uruchomienia tego wszystkiego u siebie, nie z wątku na forum.

Dwie z tych rzeczy to czysta architektura, nie kod: ta o tym, gdzie ma mieszkać wygląd strony, i ta o tym, jak zbierać adres. One kosztują najwięcej, bo żaden test ich nie łapie.

Słowo o tonie, bo taka lista łatwo czyta się jak pretensja. Nie jest nią. Nic tu nie jest zgłoszeniem błędu: wtyczki robią to, co miały robić, tylko projekt nie został nigdzie spisany, a kilka jego kawałków zachowuje się odwrotnie, niż podpowiada intuicja. Jedna rzecz wyglądała na prawdziwy defekt, więc sprawdziłem ją osobno, zamiast zakładać. Okazała się zabezpieczona, a wynik zmienił to, co mogę uczciwie napisać. Ten test jest w pułapce 04.

Pułapka 01 · licencja

Klucz licencyjny niczego nie odblokowuje

Objaw

Czekasz na klucz licencyjny od klienta, bo przecież bez niego płatne funkcje nie ruszą.

Co się naprawdę dzieje

GP Premium i GenerateBlocks Pro to osobne pliki ZIP z panelu konta na generatepress.com. To nie jest klucz, który odblokowuje darmową wtyczkę. Zainstalowałem i aktywowałem obie bez żadnego klucza: bloki Pro się rejestrują (formularz, site header, nawigacja, menu toggle, klasyczne menu), typ wpisu Global Styles się rejestruje, strona chodzi. Klucz obsługuje automatyczne aktualizacje i nic poza tym.

Obejście

Proś klienta o PLIKI, a nie o klucz. Klucz jest jego sekretem i nie musi lądować na twoim dysku. Poproś o ZIP-y i zacznij tego samego dnia.

Przy okazji: regulamin GenerateBlocks nie stawia żadnego limitu liczby stron. Zabrania odsprzedaży klucza klientom i tyle. Krążąca liczba „500 stron” pochodzi z blogów, nie od producenta, więc nie powtarzaj jej klientowi. Powiedz mu, żeby sprawdził swój plan, i uprzedź, że sklonowana strona zabiera klucz ze sobą i spróbuje się nim aktywować drugi raz.

Pułapka 02 · moduły

Moduły są domyślnie wyłączone

Objaw

Wtyczka jest zainstalowana, jest aktywna, a bloku albo funkcji, za którą zapłaciłeś, po prostu nie ma w edytorze.

Co się naprawdę dzieje

Moduły są opcjonalne i na świeżej instalacji każdy jest wyłączony. Nie ma błędu ani komunikatu, funkcji zwyczajnie nie ma. Trzy z nich są istotne:

  • enable_forms: moduł formularzy GB Pro, klucz w tablicy opcji generateblocks.
  • generateblocks_pro_classic_menu_support: klasyczne menu WordPressa w blokach nawigacji.
  • generate_package_menu_plus: jeden z modułów GP Premium; każdy ma własną opcję, a sprawdzenie porównuje z dosłownym ciągiem „activated”.
Obejście

Skrypt, który stawia stronę, musi te opcje ustawić i potem sprawdzić, a nie zakładać, że są włączone. Tablica opcji jest scalana z domyślnymi przez wp_parse_args, więc zapisanie częściowej tablicy jest bezpieczne.

PHP · provisioning
// GB Pro forms. The option array is merged with defaults via wp_parse_args,
// so writing a partial array back does not wipe the rest.
$gb_options = get_option( 'generateblocks', array() );
$gb_options['enable_forms'] = true;
update_option( 'generateblocks', $gb_options );

// Classic WP menus inside the GB Pro navigation blocks.
update_option( 'generateblocks_pro_classic_menu_support', true );

// GP Premium modules each carry their own option, and
// generatepress_is_module_active() compares against exactly this string.
update_option( 'generate_package_menu_plus', 'activated' );
Pułapka 03 · sticky header

Sticky menu siedzi w innej wtyczce

Objaw

Włączasz sticky navigation w Menu Plus z GP Premium, odświeżasz stronę i nic się nie klei. Bez błędu, bez ostrzeżenia, w ogóle nic się nie dzieje.

Co się naprawdę dzieje

Menu Plus działa na WŁASNYM nagłówku i nawigacji motywu GeneratePress. Jeżeli nagłówek strony jest zbudowany z bloków w treści, a tak wygląda typowy landing, Menu Plus nie ma czego przykleić. To nie jest zepsute, tylko wycelowane gdzie indziej.

Obejście

Użyj bloku Site Header z GB Pro, który ma sticky w środku. Jego skrypt czyta atrybuty prosto z bloku, a atrybut z punktem granicznym przyjmuje pełne media query, więc „sticky tylko na mobile” jest gotową funkcją, a nie ręcznie dłubanym CSS-em. Skrypt ładuje się dopiero wtedy, gdy blok faktycznie renderuje się jako przyklejony.

HTML · Site Header (GB Pro)
data-gb-is-sticky="true"
data-gb-sticky-breakpoint="(max-width: 767px)"   // takes a media query
data-gb-sticky-header-type="scroll-up"           // always | scroll-up | past-threshold
data-gb-sticky-animation="slide"                 // slide | fade | none
data-gb-sticky-threshold="200"
data-gb-sticky-speed="400"

Dwa szczegóły, które oszczędzają godzinę. Gdy blok jest przyklejony, GenerateBlocks dokłada mu klasę gb-is-sticky i to jest twój uchwyt na cień albo zmniejszenie. A sam blok Site Header w ogóle nie przyjmuje własnej nazwy klasy, bo deklaruje, że jej nie obsługuje, więc CSS wycelowany we własną klasę na tym bloku nie trafia w nic.

Pułapka 04 · formularze

Formularz to nie markup

Objaw

Szukasz formularza w treści strony, a jego tam nie ma. Znajdujesz tylko blok z liczbą w środku.

Co się naprawdę dzieje

Formularz GB Pro to WPIS własnego typu, którego konfiguracja siedzi w post meta. Strona osadza go blokiem renderującym, który zawiera numeryczny identyfikator formularza. Przycisk wysyłki nie jest nawet blokiem formularza: to zwykły blok tekstowy ze znacznikiem przycisku i atrybutem typu submit, i właśnie tego szuka skrypt na froncie.

  • gblocks_form: typ wpisu. Konfiguracja w post meta _gb_form.
  • generateblocks-pro/form-render: blok w stronie, z numerycznym formId.

Ma to skutek, który ludzie odkrywają późno. Klon kopiujący bazę zabiera formularz razem z jego identyfikatorem i wszystko działa. Strona odtworzona z samego motywu i wzorców dostaje martwe formId i osadzenie, które nie renderuje niczego. Skrypt, który stawia twoje strony, musi sprawdzić, czy formularz istnieje, tak samo, jak sprawdza listę wtyczek.

Obejście

Nie trzymaj adresu odbiorcy w formularzu. Adres wpisany do formularza dziedziczy każda strona z niego sklonowana, więc późniejsza zmiana oznacza edycję ich wszystkich. Zostaw pole puste, a GenerateBlocks użyje awaryjnie adresu administratora, a właściwego odbiorcę podstaw w motywie potomnym:

PHP · child theme
add_filter( 'generateblocks_form_email_args', function ( array $email_args ): array {
    $site_email = get_theme_mod( 'site_contact_email', '' );

    if ( '' !== $site_email && is_email( $site_email ) ) {
        $email_args['to'] = array( $site_email );
    }

    // Untouched -> GB's admin_email fallback still delivers.
    return $email_args;
} );

Ustaw też pole reply-to na adres e-mail z samego formularza, żeby odpowiedź trafiała do osoby, która pisze, a nie do strony.

Pułapka automatyzacji, nie defekt wtyczki

Plan „wystawiamy formularz z podpiętym webhookiem, klient wklei URL później” nie może zadziałać. Akcja webhooka waliduje swój URL i na pustym zwraca błąd, a procesor formularza sprawdza wszystkie akcje, ZANIM wykona którąkolwiek. Webhook bez URL-a przewraca więc całą wysyłkę, zanim akcja e-mail w ogóle wystartuje, i lead nie dociera do nikogo.

Zakładałem, że to dziura we wtyczce, bo webhooka ustawialiśmy zawsze skryptem. Nie jest. Wiem to tylko dlatego, że poszedłem sprawdzić. W panelu przełącznik faktycznie daje się włączyć, pole URL pojawia się puste i nie jest nawet oznaczone jako wymagane, więc do tego momentu wygląda to na dziurę. A potem zapis zostaje wprost odrzucony: „Updating failed. Webhook URL is not configured”. Zapisana konfiguracja zostaje nietknięta. Z panelu w ten zepsuty stan po prostu nie da się wejść.

To zabezpieczenie siedzi na zapisie przez REST, czyli na drodze, którą chodzi człowiek. Skrypt provisioningowy albo klonujący pisze prosto w post meta i mija go bokiem. Jedyny sposób, żeby zepsuć ten formularz, to zepsuć go samemu, z kodu. A z kodu stawia strony właśnie twój skrypt provisioningowy.

Obejście

Wystawiaj webhook wyłączony i opisz ten jeden przełącznik: edytor formularza, prawy panel, zakładka Form, sekcja Form Settings, na samym dole, obok zapisu zgłoszeń i opcji antyspamowych. Jeżeli stawiasz formularze kodem, albo zapisuj je przez ten sam endpoint REST, którego używa edytor, żeby zabezpieczenie obejmowało też ciebie, albo powtórz jego walidację u siebie, zanim zapiszesz meta.

I zauważ, kto tej szkody nie widzi. Odwiedzający dostaje komunikat, że formularz nie jest w pełni skonfigurowany. Właściciel strony nie dostaje nic: ani e-maila, ani zapisanego zgłoszenia, bo zapis zgłoszeń też jest domyślnie wyłączony. Strata jest cicha wyłącznie po tej stronie, która za nią płaci.

Ostatnia uwaga, o dowodzeniu, że most z adresem naprawdę działa. Zanim zaczniesz testować, ustaw w panelu adres INNY niż adres administratora strony, potem wyślij formularz naprawdę i sprawdź, gdzie list doleciał. Jeżeli oba adresy są takie same, dostarczony e-mail niczego nie dowodzi: przyszedłby i tak.

Pułapka 05 · bloki dynamiczne

Blok GB to też nie markup

Objaw A

Piszesz ręcznie markup GenerateBlocks do pliku wzorca. Na froncie wrapper w ogóle się nie pojawia, zostaje sama treść ze środka. Albo edytor otwiera stronę i mówi, że blok zawiera nieoczekiwaną lub nieprawidłową zawartość.

Objaw B

Zmieniasz skryptem atrybuty stylów w bloku GB Pro. Zserializowany markup wygląda dokładnie tak, jak powinien. Strona wygląda dokładnie tak, jak przedtem.

Co się naprawdę dzieje

Oba objawy mają tę samą przyczynę: blok GenerateBlocks jest dynamiczny, a to, co faktycznie się renderuje, wylicza edytor, nie ty.

Kontenery i gridy renderują się na serwerze. PHP buduje wrapper sekcji tylko wtedy, gdy blok ma unikalny identyfikator, a ten identyfikator nadaje JavaScript edytora w momencie montowania bloku. Nie ma go w surowym markupie, więc ręcznie napisany markup nie renderuje wrappera. Nawet createBlock, po którym od razu wywołasz serialize, da PUSTY identyfikator, bo efekt, który go wypełnia, jeszcze się nie wykonał.

Nowsze bloki GB Pro robią to samo, tylko piętro wyżej. Trzymają obiekt styles ORAZ skompilowany atrybut css, a front czyta css. GenerateBlocks kompiluje jedno w drugie w edytorze, przy montowaniu bloku. Zaktualizuj same styles, a blok zostanie ze starym css: markup jest dobry, strona bez zmian, nic nie zgłasza błędu. To kosztowało mnie jeden zły render, zanim zauważyłem, a zauważyłem tylko dlatego, że odczytałem getComputedStyle na froncie, zamiast wierzyć markupowi.

Obejście

Twórz markup wewnątrz załadowanego edytora, daj blokowi czas na zamontowanie się i dopiero potem go przechwyć. Przechwycony markup ma prawdziwy unikalny identyfikator i skompilowany css, renderuje się na froncie, otwiera się czysto w edytorze i nadal działa, gdy plik wzorca wstawi go do strony.

JS · in the block editor
const section = wp.blocks.createBlock( 'generateblocks/container', attributes, children );
wp.data.dispatch( 'core/block-editor' ).resetBlocks( [ section ] );

// GB assigns uniqueId and compiles styles -> css on MOUNT. Serializing sooner
// captures an empty id and stale css, and both fail silently.
await new Promise( ( resolve ) => setTimeout( resolve, 3500 ) );

const patternMarkup = wp.blocks.serialize(
  wp.data.select( 'core/block-editor' ).getBlocks()[ 0 ]
);

// Never mutate a Pro block's styles in place: re-insert it, so the mount path
// recompiles css. Carry innerBlocks across or the content is lost.
wp.data.dispatch( 'core/block-editor' ).replaceBlock(
  stale.clientId,
  wp.blocks.createBlock( stale.name, finalStyles, stale.innerBlocks )
);

Potem sprawdzaj OBA końce, za każdym razem, bo każdy potrafi przejść, gdy drugi leży: pobierz front curlem i poszukaj klasy kontenera, a potem otwórz stronę w edytorze i policz ostrzeżenia bloków, których musi być zero.

Pułapka 06 · global styles

Global Styles są martwe, a panel to pułapka

Objaw

Robisz wszystko według tutoriala do Global Styles w GenerateBlocks, a panel pokazuje „Global Styles (Legacy)” i nie pozwala niczego utworzyć.

Co się naprawdę dzieje

Stary typ wpisu jest wycofany i nie pozwala już tworzyć nowych wpisów. Żywy system to globalne klasy: jeden wpis na klasę, trzymający selektor, dane stylu i skompilowany CSS. Ten CSS kompiluje się W PRZEGLĄDARCE, a serwer nigdy go nie przelicza, co oznacza, że kod zapisujący te rekordy prosto do bazy musi wygenerować CSS sam, bo inaczej klasa istnieje i nic nie robi. Tworzy się je w edytorze bloków, w panelu bocznym bloku, a nie w kokpicie, który je tylko wypisuje i porządkuje.

Powiązany zgrzyt, jeżeli automatyzujesz edytor: zapis wpisu nie zapisuje wszystkiego. Globalne klasy są osobnymi encjami, a WordPress chowa je za drugim oknem potwierdzenia, z osobnym polem wyboru na encję. Wartość dociera do bazy dopiero po kliknięciu tego drugiego Zapisz, więc skrypt, który wywoła zapis i pójdzie dalej, nie zapisał nic. Sprawdzaj w bazie, nie w stanie edytora.

Najdroższa z nich, i to nie jest bug

Jeżeli budujesz JEDEN szablon-master do sklonowania na wiele bliźniaczych stron (sieć stron lokalizacyjnych, franczyzę, sieć witryn firmowych), wygląd należy do KODU, a nie do panelu. Systemy „global styles” w builderach trzymają design w bazie KAŻDEJ strony, a przy skali obraca się to przeciwko tobie:

  • Globalna zmiana staje się migracją. „Mniej zaokrąglone karty wszędzie” to jeden plik i skrypt, który to rozsyła, kiedy wygląd siedzi w kodzie. Kiedy siedzi w pięćdziesięciu bazach, to migracja pięćdziesięciu baz, która nadpisuje wszystko, co klient poprawił ręcznie na pojedynczych stronach.
  • Dryf. Sens mastera polega na tym, że strony są identyczne i różnią się tylko tym, czym mają się różnić: nazwą, logo, kolorem, telefonem, adresem. Design edytowalny na każdej stronie z osobna oznacza, że rok później każda jest trochę inna i żadna nie jest już masterem.

Design edytowalny z panelu jest właściwym wyborem wtedy, gdy każda strona ma wyglądać inaczej. Master do klonowania buduje się dokładnie po to, żeby wyglądały tak samo. Zauważ tę różnicę, zanim obiecasz panel, bo to decyzja projektowa, a nie techniczna, i żadne testowanie jej nie złapie. Obiecałem kiedyś panel, na piśmie, przy projekcie, w którym był złym pomysłem. Wycofałem się z tego również na piśmie.

Jest jeszcze druga połowa i to ona jest luką, której nikt nie spisuje. Dynamic Data w GenerateBlocks czyta dane wpisu, post meta, terminy, dane autora i obrazy. Źródła z Customizera NIE MA. Czyli cokolwiek klient ustawi w panelu Customizera, nazwa firmy, numer telefonu, adres, kolor marki, nie dojdzie samym Dynamic Data ani do szablonu treści, ani do jego tekstów, ani do JSON-LD. Most musi zbudować motyw potomny: shortcode albo własne źródło danych, plus PHP, które wypisze schemę. Wyceniaj ten most jako prawdziwą robotę, nie jako markup. To zresztą dokładnie ta rzecz, dzięki której skrypt klonujący nie musi podmieniać numeru telefonu na każdej stronie każdej sklonowanej witryny.

Pułapka 07 · obudowa motywu

Motyw dokłada drugą stopkę

Objaw

Twój landing zbudowany z bloków wychodzi wąski, z tytułem strony, którego nie dodawałeś, i z DRUGĄ stopką pod twoją. Ta druga stopka zawiera podpis „Built with GeneratePress” i jedzie na żywą stronę klienta.

Co się naprawdę dzieje

Nic z tego nie jest twoim markupem. To obudowa motywu, dokładana dookoła twojej treści, i będzie się dokładać, dopóki nie zdejmiesz każdego kawałka osobno. Trzy z czterech mają filtry albo akcje. Czwarty nie ma i to on gryzie.

Obejście

Pełna szerokość nie jest filtrem. To pole wyboru per strona, zapisane w post meta, a GeneratePress czyta je prosto z meta, bez żadnego filtra na wartość, więc strona utworzona skryptem wychodzi wąska. Podstaw wartość domyślną przez sam odczyt meta, zabezpieczając się przed wejściem w rekurencję, i podstawiaj tylko przypadek NIEUSTAWIONY, żeby świadomy wybór na konkretnej stronie nadal wygrywał.

PHP · child theme
add_filter( 'generate_show_title', '__return_false' );
add_filter( 'generate_sidebar_layout', fn() => 'no-sidebar' );

remove_action( 'generate_header', 'generate_construct_header' );

// Forget this line and the client's site ships a second footer
// carrying a "Built with GeneratePress" credit.
remove_action( 'generate_footer', 'generate_construct_footer' );

// Full width is post meta with no filter on the value. Default the unset case only.
add_filter( 'get_post_metadata', function ( $short_circuit, $post_id, $meta_key, $is_single ) {
    static $reading = false;

    if ( $reading || '_generate-full-width-content' !== $meta_key ) {
        return $short_circuit;
    }

    $reading = true;
    $stored_flag = get_post_meta( $post_id, $meta_key, true );
    $reading = false;

    // An array serves both the single and the multiple read.
    return '' === $stored_flag ? array( 'true' ) : $short_circuit;
}, 10, 4 );

Filtr od sidebara warto zostawić, nawet jeżeli strona dziś nie ma sidebara: układ bierze się z domyślnej wartości motywu, a od zepsucia wszystkich landingów dzieli ją jedno kliknięcie w Customizerze.

Pułapka 08 · bonus, czysty WordPress

Schema, która przechodzi walidator i nic nie daje

Objaw

Markup LocalBusiness jest na stronie. Walidator świeci na zielono. Mijają miesiące i lokalnych rich resultów nie ma.

Co się naprawdę dzieje

W dokumentacji danych strukturalnych Google adres jest właściwością WYMAGANĄ i musi być obiektem PostalAddress z rozbitymi częściami. Adres jako zwykły ciąg znaków to legalne schema.org. Przechodzi walidatory i nie kwalifikuje strony do lokalnych rich resultów. Markup jest i nic nie robi, a to najgorszy rodzaj awarii, bo wygląda na skończony.

JSON-LD · the part that counts
"address": {
  "@type": "PostalAddress",
  "streetAddress": "Kwiatowa 12",
  "addressLocality": "Warszawa",
  "postalCode": "00-001",
  "addressCountry": "PL"
}
Obejście

Zbieraj adres W KAWAŁKACH, jako osobne pola: ulica, miasto, kod pocztowy, kod kraju. Nie bierz jednego pola tekstowego i nie dziel go zgadywaniem. Zgadywany podział wytrzymuje dokładnie do pierwszego adresu zapisanego inaczej, niż zakładałeś, i wtedy psuje się po cichu: strona wygląda identycznie, a dane strukturalne są nieprawidłowe. Widoczną linię w stopce buduj Z TYCH SAMYCH kawałków, żeby wyświetlanie i schema nie mogły się rozjechać.

Jeszcze dwa drobiazgi. Kraj chce dwuliterowego kodu, więc waliduj go wzorcem, zamiast opublikować literówkę na każdej stronie. I trzymaj kraj POZA widoczną linią w stopce: lokalnemu klientowi nie trzeba mówić, w jakim jest kraju, a Google i tak czyta JSON-LD, nie stopkę.

Wniosek

Prawie żadna z nich nie rzuca błędu

Zobacz, co te rzeczy mają wspólnego. Unikalny identyfikator, którego nie ma w markupie. Atrybut css, który został stary. Moduł, który jest po prostu wyłączony. Konfiguracja formularza, którą skrypt potrafi wpisać w stan zabroniony w panelu. Schema, która przechodzi walidator i nic nie daje. Prawie w każdej z nich stan zepsuty wygląda identycznie jak stan działający i dlatego docierają na produkcję: nie ma czego zobaczyć ani czego wyszukać.

Nic z tego nie jest defektem i właśnie to jest w tym najcenniejsze. Każda z tych rzeczy to decyzja projektowa, której nikt nie spisał, co oznacza, że żadne zgłoszenie błędu tego nie naprawi i żadna aktualizacja tego nie usunie. To wiedza, a nie zgłoszenie.

Metoda, która je łapie, jest nudna. Czytaj źródła wtyczki zamiast wpisu na blogu, bo wpis na blogu to zwykle zapowiedź producenta, tyle że wygładzona. Sprawdzaj wynik drogą niezależną od tej, która go wyprodukowała: front, nie edytor; baza, nie panel; getComputedStyle, nie markup, który przed chwilą napisałeś. Test idący tą samą drogą co kod dowodzi tylko tego, że kod zgadza się sam ze sobą.

Działa to w obie strony i właśnie dlatego to piszę przy webhooku. Przez tygodnie miałem go zanotowanego jako dziurę we wtyczce, na solidnej podstawie: przeczytałem źródło, a źródło się nie myliło. Nie zrobiłem tylko jednej rzeczy, czyli nie spróbowałem tak, jak robi to człowiek. Pięć minut w panelu zamieniło zgłoszenie błędu w znacznie użyteczniejsze zdanie: zabezpieczenie istnieje, a to mój skrypt je obchodził.

A te dwie architektoniczne są prawdziwe niezależnie od tego, którego buildera wybierzesz. Jeżeli wygląd ma być identyczny na wielu stronach, nie należy do panelu. Jeżeli adres ma być czytelny dla maszyny, nie należy do jednego pola tekstowego.

Budujesz na GeneratePress albo GenerateBlocks?

Napisz, co strona ma robić. Powiem ci, gdzie ten stack stawi opór, zanim zapłacisz za pierwszą godzinę.

Zapytaj o swoją stronę