Terza puntata di Faccio cose, vedo gente, la serie in cui racconto i plugin WordPress fatti su misura per i clienti che altrimenti resterebbero chiusi in un repository. Questa volta il problema è uno di quelli che prima o poi capitano a chiunque gestisca siti WordPress da un po’ di anni: passare da WPBakery a Gutenberg senza rifare le pagine a mano. Il cliente aveva un sito con un tema comprato anni fa, decine di pagine costruite con il page builder e nessuna voglia, di rifarle una per una.
In questo articolo ti racconto come ho affrontato la migrazione con un plugin scritto apposta: un parser che legge gli shortcode annidati, una mappa che li trasforma in blocchi nativi, il CSS del builder, il backup, la conversione in batch e tanto altro. Il codice in questo caso non è pubblico perché contiene i converter specifici del tema del cliente, ma se ti serve si può adattare; quelli che ho reso pubblici li trovi invece nella pagina dei miei plugin WordPress.
Decine di pagine di shortcode
Un tema ThemeForest con WPBakery, una libreria di shortcode proprietari e decine di pagine da portare nell’editor a blocchi di Gutenberg.
Il sito era nato anni fa con un tema comprato su ThemeForest che si appoggia a WPBakery Page Builder e a una libreria di shortcode propri, quelli con il prefisso nd_: titoli colorati, spaziatori, contatori animati, tabelle prezzi, schede dei servizi. Il risultato, aprendo una pagina nell’editor, era un muro di parentesi quadre, con testi e pulsanti annidati su quattro o cinque livelli.
Il problema del cliente non era estetico: le pagine funzionavano. Il problema era migrare su qualcosa di più semplice, moderno e veloce. Dal mio punto di vista l’unica strada possibile era Gutenberg, cioè l’editor a blocchi che oggi è lo standard di WordPress e che, dalla versione 7.0 in poi, è anche la base su cui arrivano le funzioni di AI nativa.
Passare da WPBakery a Gutenberg, però, non è un interruttore. Disattivando il builder, WordPress mostra gli shortcode come testo. Attivando Gutenberg con WPBakery ancora attivo, ogni pagina diventa un unico blocco Classico pieno di parentesi quadre. Serviva qualcosa che leggesse quelle parentesi e le trasformasse in blocchi veri, con gli stessi contenuti e, dove possibile, gli stessi stili.

Perché non rifarle a mano
Ricopiare decine di pagine costa più di un plugin, si sbaglia e non si ripete. Un convertitore da WPBakery a Gutenberg si scrive una volta, si prova sulle pagine di esempio e si lancia su tutto il sito, con il backup pronto.
La prima tentazione è sempre quella: «sono solo pagine, le rifacciamo». Con tre pagine funziona. Con decine, ciascuna con sezioni, colonne, immagini con dimensioni precise, pulsanti con i colori del brand e tabelle prezzi, i conti cambiano: ore di lavoro ripetitivo, errori di copia, e la certezza che la pagina numero quaranta sarà fatta peggio della numero quattro. E se il cliente a metà strada cambia idea su un dettaglio, si ricomincia.
La seconda tentazione è cercare un plugin già pronto. Stando alle mie ricerche di febbraio, quando il progetto è partito, gli strumenti generici convertono bene gli shortcode standard di WPBakery ma ignorano quelli dei temi, e qui gli shortcode del tema erano la metà del contenuto. Il risultato sarebbe stato una pagina con i testi a posto e tutte le parti «belle», dai contatori alle tabelle prezzi, trasformate in righe di parentesi quadre.
Così ho scelto la terza strada: un plugin su misura che conosce sia gli shortcode di WPBakery sia quelli del tema, che si prova sulle pagine reali esportate dal sito, che fa un backup prima di toccare qualunque cosa e che si può lanciare su tutte le pagine da una tabella con filtri e barra di avanzamento. L’ho costruito con Claud.ia, con il metodo della guida a Claude Code, e con due colleghi che hanno creato delle pagine di esempio con casi limite.
Pensavo fosse un lavorone pieno di casi particolari. Invece il grosso del passaggio da WPBakery a Gutenberg è arrivato in fretta, e il tempo l’ha preso quello che non si vede: la validazione dei blocchi.
Un parser al posto delle regex
Gli shortcode di WPBakery sono annidati, usano lo stesso nome a livelli diversi e mescolano HTML e parentesi quadre: per passare da WPBakery a Gutenberg una regex non basta, serve un parser ricorsivo che costruisca un albero.
La prima versione di chiunque affronti questo problema usa le espressioni regolari: cerca [vc_row, trova la chiusura, estrai il contenuto. Funziona finché dentro una riga non c’è una vc_row_inner, dentro una colonna un’altra colonna, e dentro un testo un pezzo di HTML con parentesi quadre che non sono shortcode. A quel punto la regex «non avida» si ferma alla prima chiusura che trova, che è quella sbagliata, e la pagina esce tagliata a metà.
Il plugin usa invece un parser ricorsivo discendente che legge il contenuto carattere per carattere e costruisce un albero di token: ogni shortcode con il suo nome, i suoi attributi e i suoi figli. L’annidamento può essere profondo quanto vuole, i tag omonimi a livelli diversi non si confondono e il testo libero tra uno shortcode e l’altro resta al suo posto. Il parser ha una lista di tag contenitore, quelli che hanno una chiusura, e tratta tutto il resto come foglia.

Una mappa da shortcode a blocco
Ogni shortcode ha un converter in una classe sua, registrata in un registro: 26 tag WPBakery, 28 tag del tema, e un fallback che conserva in un blocco HTML tutto ciò che non si riconosce.
Una volta ottenuto l’albero, ogni nodo passa da un registro che, in base al nome del tag, sceglie la classe che sa convertirlo. Un converter per file, un’interfaccia comune, una classe astratta con gli helper che servono a tutti: leggere un attributo, risolvere un’immagine dal suo id all’URL con alt e dimensioni, decodificare un link nel formato strano di WPBakery (url:…|title:…|target:…), ricostruire lo shortcode originale quando non c’è di meglio. Il registro è estendibile con un’azione WordPress, così i converter di un altro tema possono vivere in un plugin a parte.
Le corrispondenze principali sono quelle che ti aspetti. vc_row e vc_column diventano core/columns e core/column, con le larghezze tradotte dalle frazioni («1/3», «2/3») in percentuali. vc_column_text diventa uno o più paragrafi, e dalle versioni più recenti anche titoli e liste native quando dentro c’è un h2 o una ul. vc_single_image diventa un’immagine con dimensioni e link, vc_btn un pulsante, vc_empty_space uno spacer, vc_toggle e gli accordion dei dettagli, vc_raw_html un blocco HTML dopo una doppia decodifica, base64 e poi URL, perché WPBakery lo salva così.
Per gli shortcode del tema ho scritto cinque converter specifici, per i titoli con colore e dimensione, gli spaziatori, i contatori, le tabelle prezzi e le schede servizi, che producono blocchi nativi o HTML già renderizzato con lo stesso aspetto del sito. Gli altri ventitré tag passano dal converter generico e finiscono in un blocco core/html con il markup originale, così nulla si perde e il cliente vede subito cosa è rimasto da sistemare a mano. Il fallback vale anche per qualunque shortcode sconosciuto: mai cancellare contenuto, al massimo lasciarlo com’era.
- Layout: vc_row, vc_row_inner, vc_column, vc_column_inner, vc_section → colonne e gruppi.
- Contenuto: vc_column_text, vc_custom_heading, vc_single_image, vc_video, vc_separator, vc_empty_space, vc_raw_html, vc_message, vc_icon → paragrafi, titoli, immagini, embed, separatori, spacer, HTML.
- Interazione: vc_btn, vc_cta, vc_tabs, vc_tta_tabs, vc_accordion, vc_tta_accordion, vc_toggle → pulsanti, dettagli e gruppi.
- Tema: nd_options_text, nd_options_spacer, nd_options_counter, nd_options_prices, nd_options_services con converter dedicati; gli altri nd_* in core/html.
Il blocco contiene contenuto inaspettato
Gutenberg confronta l’HTML salvato con quello che genererebbe dagli attributi JSON del blocco: se non coincidono, il blocco è «invalido». Metà del lavoro di una migrazione da WPBakery a Gutenberg sta qui.
Un blocco Gutenberg salvato nel database è fatto di due parti: un commento HTML con gli attributi in JSON e il markup vero e proprio. Quando apri la pagina nell’editor, ogni blocco ricalcola il markup a partire dagli attributi con la propria funzione save() e lo confronta con quello salvato. Se c’è una differenza, anche una classe in più o in meno, compare l’avviso «Questo blocco contiene contenuto inaspettato» e l’editor propone di recuperarlo o di convertirlo in HTML. Per una pagina va bene. Per decine di pagine con centinaia di blocchi è una migrazione fallita.
I primi converter scrivevano gli attributi di stile nel JSON, colore di sfondo, bordi arrotondati, margini, e non li riflettevano nel markup del contenitore. Il risultato era sistematico: ogni colonna con uno sfondo era invalida. La soluzione è stata un helper nella classe astratta che centralizza la serializzazione del wrapper: se l’attributo dice che c’è uno sfondo, il div deve avere la classe has-background e lo stile inline corrispondente; se c’è un colore del testo, has-text-color; se c’è un allineamento, has-text-align-center e sorelle. Una regola sola, applicata a tutti i converter, e l’avviso è sparito da tutte le colonne.
Poi sono arrivati i casi particolari, uno alla volta, ciascuno con la sua riga nel changelog. Il pulsante core/button ha attributi come url, linkTarget e rel che nel block.json sono dichiarati con source: attribute: Gutenberg li legge dal tag a nell’HTML, non dal JSON, e se li trova nel JSON li considera un errore. Lo spacer non si può scrivere come blocco autochiudente, va serializzato per intero con il suo div.
Un margin-bottom: 0px ereditato dal builder, scritto nel JSON e non nel markup, invalida il blocco: i valori zero ora vengono ignorati, sono rumore. E il line-height in pixel, che Gutenberg vuole senza unità, resta solo nello stile inline.
L’ultimo capitolo riguarda il contenuto misto dentro vc_column_text. Un testo con un titolo e una lista, avvolto in un paragrafo, produce HTML non valido (un h3 dentro un p) e di nuovo l’avviso. Ora il converter spezza il contenuto in blocchi distinti, paragrafi, titoli e liste native, e per le liste annidate usa tre piccoli tokenizzatori che contano la profondità invece di una regex, perché la regex troncava la lista esterna alla prima chiusura interna. Ogni volta la morale è la stessa: il markup deve essere esattamente quello che Gutenberg scriverebbe, non uno che gli assomiglia.

CSS, griglie e backup
Nel passaggio da WPBakery a Gutenberg il CSS del builder viene mappato sugli attributi dei blocchi dove si può e raccolto in un blocco di stile dove non si può; le griglie strane vengono segnalate; il contenuto originale resta in un backup ripristinabile.
WPBakery salva gli stili in un attributo css con una regola per classe generata, tipo .vc_custom_1467207947647{padding-top: 10px !important;}. Il plugin ha un piccolo parser CSS che legge quelle regole e un mappatore che traduce le proprietà note in attributi nativi: colore di sfondo, colore del testo, padding, margini. Quello che non si può tradurre, per esempio un’immagine di sfondo con posizione e ripetizione, finisce in un collettore e viene scritto come blocco HTML con un tag style in testa alla pagina, così il rendering non dipende da nulla di esterno al contenuto.
Le griglie sono il secondo terreno scivoloso. Una riga con sei colonne, una colonna da «5/12», colonne che insieme non fanno uno, offset responsive del tipo vc_col-md-4: in Gutenberg non hanno un equivalente diretto. Un analizzatore guarda ogni riga e, se la trova sospetta, la conversione si ferma e una modale chiede cosa fare: colonne Gutenberg, un gruppo, o HTML. Nella conversione in batch le pagine con griglie di questo tipo vengono saltate e segnalate nel report, da fare a mano dall’editor.
E il backup. Prima di toccare una pagina il contenuto originale finisce in un meta del post, insieme alla data e alle statistiche della conversione (quanti shortcode convertiti, quanti finiti nel fallback e quali). Il backup non viene mai sovrascritto dalle riconversioni successive, così anche dopo tre tentativi si torna sempre all’originale con un clic su «Ripristina». Un pulsante a parte pulisce i meta del plugin quando la migrazione è chiusa e il cliente è contento. Il tutto passa da endpoint REST con il controllo del permesso edit_post sulla singola pagina.
Da WPBakery a Gutenberg in batch
Una pagina alla volta dalla sidebar dell’editor, oppure tutte insieme da Strumenti: tabella con filtri, azioni di gruppo, barra di avanzamento in una modale e report finale con i link alle pagine da controllare.
Nell’editor il plugin aggiunge una sidebar con il pulsante «Converti in Gutenberg», lo stato della pagina (convertita quando, con quante statistiche), il ripristino e la pulizia. Dopo una conversione riuscita la pagina si ricarica da sola, perché l’editor non riesce a sostituire al volo tutto il contenuto senza lasciare in giro pezzi dello stato precedente. È lo strumento per portare da WPBakery a Gutenberg le pagine delicate, quelle che si vogliono guardare una per una.
Per tutte le altre c’è la pagina Strumenti > WPBakery Batch Converter: una tabella paginata di tutti i contenuti, con filtri per titolo, tipo di contenuto, stato di conversione e presenza di shortcode [vc_ nel testo, una colonna con lo stato di pubblicazione e la data, e il pulsante «Converti» su ogni riga. Selezioni le pagine, scegli l’azione (converti, ripristina, pulisci) e una modale mostra la barra di avanzamento con il titolo della pagina in lavorazione e il pulsante per fermarsi.
Il batch gira in sequenza lato client: una richiesta REST per pagina, così un errore su una non ferma le altre e il server non riceve mai cento conversioni insieme. Alla fine un report dice quante sono andate bene, quante sono state saltate per le griglie complesse e quante hanno dato errore, con il link all’editor per ciascuna. Per passare da WPBakery a Gutenberg su tutto il sito del cliente è bastato un pomeriggio di conversioni, controlli e qualche ritocco a mano sulle pagine con le griglie creative.

Un lavoro di squadra
Il plugin è nato in team: le pagine di esempio esportate dal sito, un collega sui converter del tema, io sul parser e sulla validazione, Claud.ia a supporto di tutto.
Questo è stato un lavoro a più mani. C’è chi ha esportato dal sito quattro pagine rappresentative che sono diventate la suite di prova del plugin: ogni modifica ai converter si lancia su quei file e si confronta il risultato. Chi ha scritto una parte dei converter e il flusso del batch. Io mi sono tenuto il parser, la validazione dei blocchi e l’architettura. E poi, ovviamente, Claud.ia, che ha scritto buona parte del codice, con un CLAUDE.md che spiega il flusso di conversione in otto passi e i formati strani di WPBakery da conoscere, dai link con i due punti ai contenuti in base64.
Il changelog è diventato la documentazione del progetto: rileggendolo per scrivere questo articolo ho ritrovato tutta la storia della migrazione da WPBakery a Gutenberg, e un cliente che un giorno chiedesse «ma perché quel pulsante è fatto così?» troverebbe la risposta già scritta. Lo consiglio a chiunque faccia plugin per altri: il changelog è documentazione per il te stesso di fra sei mesi.
Lo stack è volutamente semplice: PHP 7.4 compatibile, perché l’hosting del cliente non era aggiornatissimo, un autoloader PSR-4 scritto a mano senza Composer a runtime, e la sidebar e la pagina admin in React con @wordpress/scripts e due entry nel webpack.
E poi è arrivato anche Divi
Lo stesso plugin oggi converte anche i siti costruiti con Divi: un’astrazione per i builder, converter dedicati ai moduli et_pb_ e un passthrough che spiega cosa non si può convertire.
Il bello di un’architettura con parser, registro e fallback generici è che prima o poi qualcuno ti mette davanti un altro builder. Ad agosto è arrivato un sito costruito con Divi, che salva le pagine con shortcode [et_pb_ e una logica tutta sua, e invece di scrivere un secondo plugin abbiamo aggiunto al primo un’astrazione dei builder: ognuno dichiara il prefisso dei tag, i tag contenitore, i suoi converter, le regole delle griglie e i meta da salvare nel backup, mentre il plugin riconosce da solo se una pagina è WPBakery o Divi e avvisa quando li trova mescolati nello stesso contenuto.
I converter seguono la stessa strada di WPBakery: sezioni, righe e colonne diventano gruppi e colonne native, mentre testi, titoli, immagini, pulsanti, separatori, gallery, video, toggle, accordion e tab diventano i rispettivi blocchi core. Divi, però, ha le sue stranezze: codifica virgolette e parentesi negli attributi con sequenze come %22 e %91, salva le immagini con l’URL assoluto invece dell’id dell’allegato e scrive padding e margini separati da barre verticali. Per ognuna c’è un piccolo componente dedicato, dal decoder degli attributi al mapper degli stili, fino alla ricerca dell’allegato partendo dall’URL.
I moduli più ricchi, come blurb, call to action, testimonial e header a tutta larghezza, vengono approssimati con blocchi core e un foglio di stile leggero caricato solo sulle pagine che ne hanno bisogno, così sopravvivono alla disattivazione di Divi. Quelli dinamici per natura, dal form di contatto al blog, dallo shop al menu, restano shortcode in un blocco HTML con un avviso che spiega perché non si possono convertire. Sulle pagine Divi reali usate come prova non resta nemmeno uno shortcode sconosciuto, e le pagine WPBakery escono identiche a prima, byte per byte.
Domande frequenti
Si può passare da WPBakery a Gutenberg senza perdere i contenuti?
Sì, se gli shortcode vengono convertiti in blocchi nativi prima di disattivare il builder. Il plugin descritto qui legge il contenuto WPBakery, lo trasforma in blocchi Gutenberg e tiene l’originale in un backup ripristinabile, così da WPBakery a Gutenberg non si perde nulla.
Esistono plugin già pronti per questa migrazione?
Ce ne sono per gli shortcode standard di WPBakery, ma stando alle mie ricerche di inizio 2026 non coprono gli shortcode dei temi, che spesso sono metà del contenuto. Per quelli servono converter scritti apposta.
Perché Gutenberg dice «Questo blocco contiene contenuto inaspettato»?
Perché l’HTML salvato non coincide con quello che il blocco genererebbe dai suoi attributi JSON. Succede quando un converter scrive un attributo (uno sfondo, un margine) senza riflettere la classe e lo stile corrispondenti nel markup, o viceversa.
Cosa succede agli shortcode che il plugin non conosce?
Finiscono in un blocco HTML con il markup originale, così il contenuto resta visibile e si capisce subito cosa è rimasto da sistemare. Il plugin non cancella mai nulla.
Si possono convertire tutte le pagine insieme?
Sì, dalla pagina Strumenti > WPBakery Batch Converter: si filtrano le pagine, si selezionano e si lancia la conversione, una alla volta in sequenza, con barra di avanzamento e report finale. Le pagine con griglie complesse vengono saltate e segnalate.
Serve tenere WPBakery attivo dopo la conversione?
No, ed è il punto del passaggio da WPBakery a Gutenberg: una volta che le pagine sono blocchi nativi, il builder si può disattivare. Conviene farlo pagina per pagina o dopo un controllo visivo del frontend, tenendo i backup finché il cliente non è soddisfatto.
Il plugin da WPBakery a Gutenberg funziona con altri temi o builder?
L’architettura sì: parser, registro e fallback sono generici, e infatti oggi il plugin converte anche i siti costruiti con Divi. I converter degli shortcode del tema vanno scritti per ciascun tema, mentre per altri builder, come Elementor che salva le pagine in JSON, servirebbe un parser del loro formato.
Shortcode addio, e senza nostalgia
Un parser, un registro di converter, il rispetto maniacale del markup di Gutenberg e un backup che non si tocca: la conversione da WPBakery a Gutenberg non è perfetta, ma toglie di mezzo il grosso del lavoro.
Il cliente oggi modifica le sue pagine dall’editor a blocchi e il builder è disattivato. La conversione, sia chiaro, non è perfetta: qualche griglia creativa va rifatta a mano, i moduli più elaborati escono approssimati e un controllo visivo pagina per pagina resta obbligatorio. Però il plugin toglie di mezzo il grosso del lavoro, quello ripetitivo del ricopiare testi, immagini e pulsanti, e lascia alle persone la rifinitura, che è la parte in cui serve davvero un occhio umano.
Passare da WPBakery a Gutenberg, non è leggere gli shortcode ma scrivere blocchi che Gutenberg riconosca come suoi: ogni classe, ogni stile, ogni attributo al posto giusto. La lezione di metodo è che una suite di pagine reali e un changelog puntiglioso valgono più di qualunque architettura elegante.
Se vuoi approfondire, la documentazione degli attributi dei blocchi spiega il meccanismo di validazione e la differenza tra attributi nel JSON e attributi letti dall’HTML; l’articolo su WordPress 7.0 racconta perché conviene avere contenuti a blocchi oggi; e nella categoria WordPress trovi le puntate precedenti, dal plugin delle prenotazioni ai listini prezzi WooCommerce, e tra poco le prossime. Se hai un sito fermo a un page builder e vuoi capire quanto costa davvero migrarlo, nella pagina sulla consulenza WordPress trovi come lavoro, oppure prenota una call: si parte sempre da un’occhiata alle pagine, come è successo qui.
E tu, con quale page builder hai il conto in sospeso: WPBakery, Elementor, Divi? Scrivilo nei commenti, con il tema se te lo ricordi, perché sono curioso di sapere quali shortcode proprietari girano ancora là fuori. Se conosci qualcuno che sta per rifare a mano trenta pagine, mandagli questo articolo prima che cominci.


