Quando realizzo un sito WordPress con Oxygen Builder cerco, per quanto possibile, di evitare contenuti inseriti direttamente all’interno dei template.
Questo vale soprattutto per elementi che devono essere modificabili facilmente dal cliente, come le FAQ.
Nel mio caso avevo una necessità abbastanza precisa: aggiungere una sezione FAQ solamente ad alcune pagine del sito, permettere di gestire domande e risposte dal backend di WordPress e generare contemporaneamente i relativi dati strutturati Schema.org.
La soluzione che ho adottato utilizza:
- WordPress;
- Oxygen Builder;
- Advanced Custom Fields Pro;
- un campo Repeater ACF;
- un Code Block di Oxygen;
- JSON-LD per lo schema
FAQPage.
Il vantaggio è che domande, risposte, contenuto visualizzato e dati strutturati provengono tutti dalla stessa sorgente.
Vediamo come realizzarlo.
Di cosa parlerò in questa pagina
Il problema: gestire le FAQ senza modificarle in Oxygen
Avrei potuto creare manualmente il blocco FAQ direttamente dentro Oxygen.
La soluzione avrebbe funzionato, ma avrebbe avuto diversi limiti.
Ogni volta che fosse stato necessario:
- aggiungere una domanda;
- rimuovere una FAQ;
- modificarne la risposta;
- attivare le FAQ solamente su determinate pagine;
sarebbe stato necessario entrare nell’editor di Oxygen.
Volevo invece che la gestione dei contenuti rimanesse nel normale backend di WordPress.
La soluzione più logica era quindi utilizzare Advanced Custom Fields.
L’obiettivo finale era avere, durante la modifica di una pagina, qualcosa di simile a:
Mostra FAQ: sì
Titolo:
Domande frequenti
FAQ:
Domanda:
Quanto costa il servizio?
Risposta:
Il servizio parte da...
------------------
Domanda:
Quanto tempo richiede?
Risposta:
Normalmente...
Oxygen avrebbe poi avuto il solo compito di leggere questi dati e visualizzarli.
Perché utilizzare un Repeater ACF
Il numero di FAQ non è necessariamente uguale in tutte le pagine.
Una pagina potrebbe averne tre, un’altra sette e un’altra ancora nessuna.
Per questo motivo ho utilizzato un Repeater ACF, creando al suo interno due sottocampi:
domandarisposta
La struttura completa è:
| Etichetta | Nome campo | Tipo |
|---|---|---|
| Mostra FAQ | faq_attive | True / False |
| Titolo sezione | faq_titolo | Text |
| FAQ | faq | Repeater |
| Domanda | domanda | Text |
| Risposta | risposta | WYSIWYG |
Il campo Repeater è disponibile in ACF Pro.
Il campo faq_attive, invece, mi permette di decidere pagina per pagina se mostrare o meno l’intero blocco.
Registrare i campi ACF tramite PHP
I campi potrebbero naturalmente essere creati dall’interfaccia grafica di ACF.
Personalmente, quando la struttura fa parte integrante dello sviluppo del sito, preferisco però registrarli tramite PHP.
In questo modo la configurazione rimane nel codice e può essere trasferita più facilmente tra ambienti diversi.
Il codice è il seguente:
add_action('acf/init', function () {
if (!function_exists('acf_add_local_field_group')) {
return;
}
acf_add_local_field_group(array(
'key' => 'group_faq_page',
'title' => 'FAQ',
'fields' => array(
array(
'key' => 'field_faq_attive',
'label' => 'Mostra FAQ',
'name' => 'faq_attive',
'type' => 'true_false',
'ui' => 1,
'default_value' => 0,
),
array(
'key' => 'field_faq_titolo',
'label' => 'Titolo sezione FAQ',
'name' => 'faq_titolo',
'type' => 'text',
'default_value' => 'Domande frequenti',
'conditional_logic' => array(
array(
array(
'field' => 'field_faq_attive',
'operator' => '==',
'value' => '1',
),
),
),
),
array(
'key' => 'field_faq_repeater',
'label' => 'FAQ',
'name' => 'faq',
'type' => 'repeater',
'layout' => 'block',
'button_label' => 'Aggiungi FAQ',
'conditional_logic' => array(
array(
array(
'field' => 'field_faq_attive',
'operator' => '==',
'value' => '1',
),
),
),
'sub_fields' => array(
array(
'key' => 'field_faq_domanda',
'label' => 'Domanda',
'name' => 'domanda',
'type' => 'text',
'required' => 1,
),
array(
'key' => 'field_faq_risposta',
'label' => 'Risposta',
'name' => 'risposta',
'type' => 'wysiwyg',
'required' => 1,
'toolbar' => 'basic',
'media_upload' => 0,
'tabs' => 'visual',
),
),
),
),
'location' => array(
array(
array(
'param' => 'post_type',
'operator' => '==',
'value' => 'page',
),
),
),
'position' => 'normal',
'style' => 'default',
'label_placement' => 'top',
'active' => true,
));
});
Il codice può essere inserito, ad esempio, nel functions.php del child theme oppure, soluzione che preferisco, all’interno di un piccolo plugin personalizzato.
La regola:
'param' => 'post_type',
'operator' => '==',
'value' => 'page',
fa comparire i campi solamente nelle pagine.
Se le FAQ devono essere disponibili anche in articoli o Custom Post Type è sufficiente modificare le regole location.
Creare il blocco FAQ dentro Oxygen Builder
A questo punto rimane la parte relativa alla visualizzazione.
Nel template Oxygen ho inserito un Code Block.
L’idea interessante è che il blocco può essere presente direttamente nel template utilizzato da tutte le pagine.
Non è quindi necessario aggiungerlo manualmente ogni volta.
Il codice controlla prima il valore di:
faq_attive
Se è disabilitato non viene prodotto alcun HTML.
Se invece è attivo, vengono recuperate le FAQ presenti nel Repeater ACF.
PHP per visualizzare le FAQ in Oxygen
Nel Code Block Oxygen possiamo utilizzare:
<?php
if (
function_exists('get_field') &&
get_field('faq_attive')
) :
$faqs = get_field('faq');
$titolo = get_field('faq_titolo');
if (!$titolo) {
$titolo = 'Domande frequenti';
}
if (!empty($faqs) && is_array($faqs)) :
$schema_faq = array();
?>
<section class="faq-section">
<?php if ($titolo) : ?>
<h2 class="faq-title">
<?php echo esc_html($titolo); ?>
</h2>
<?php endif; ?>
<div class="faq-list">
<?php foreach ($faqs as $faq) :
$domanda = !empty($faq['domanda'])
? trim($faq['domanda'])
: '';
$risposta = !empty($faq['risposta'])
? $faq['risposta']
: '';
if (!$domanda || !$risposta) {
continue;
}
/*
* Creo contemporaneamente
* i dati destinati al JSON-LD.
*/
$risposta_schema = wp_strip_all_tags(
strip_shortcodes($risposta)
);
$risposta_schema = trim(
preg_replace('/\s+/', ' ', $risposta_schema)
);
$schema_faq[] = array(
'@type' => 'Question',
'name' => $domanda,
'acceptedAnswer' => array(
'@type' => 'Answer',
'text' => $risposta_schema,
),
);
?>
<details class="faq-item">
<summary class="faq-question">
<?php echo esc_html($domanda); ?>
</summary>
<div class="faq-answer">
<?php echo wp_kses_post($risposta); ?>
</div>
</details>
<?php endforeach; ?>
</div>
</section>
<?php
if (!empty($schema_faq)) :
$schema = array(
'@context' => 'https://schema.org',
'@type' => 'FAQPage',
'mainEntity' => $schema_faq,
);
?>
<script type="application/ld+json">
<?php
echo wp_json_encode(
$schema,
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
);
?>
</script>
<?php
endif;
endif;
endif;
?>
Con un solo blocco di codice otteniamo quindi due risultati:
- la visualizzazione delle FAQ;
- la generazione del JSON-LD.
Ed è proprio questo il punto più importante della soluzione.
Utilizzare <details> e <summary> per creare l’accordion
Per la parte visuale ho scelto gli elementi HTML nativi:
<details>
<summary>Domanda</summary>
<div>
Risposta
</div>
</details>
In questo modo non è necessario sviluppare un accordion JavaScript solamente per aprire e chiudere le FAQ.
Il browser gestisce nativamente il comportamento.
Il markup risultante è semplice e può essere personalizzato facilmente con CSS.
CSS di base per le FAQ
Una possibile base grafica è:
.faq-section {
width: 100%;
}
.faq-title {
margin-bottom: 30px;
}
.faq-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.faq-item {
border: 1px solid #ddd;
border-radius: 6px;
overflow: hidden;
}
.faq-question {
padding: 18px 50px 18px 20px;
cursor: pointer;
font-weight: 600;
list-style: none;
position: relative;
}
.faq-question::-webkit-details-marker {
display: none;
}
.faq-question::after {
content: "+";
position: absolute;
right: 20px;
top: 50%;
transform: translateY(-50%);
font-size: 24px;
}
.faq-item[open] .faq-question::after {
content: "−";
}
.faq-answer {
padding: 0 20px 20px;
}
.faq-answer > *:first-child {
margin-top: 0;
}
.faq-answer > *:last-child {
margin-bottom: 0;
}
Naturalmente, utilizzando Oxygen, colori, dimensioni, spaziature e tipografia possono essere adattati alla grafica del sito.
Generare automaticamente i dati strutturati FAQPage
Mentre vengono visualizzate le FAQ, lo script costruisce anche un array PHP contenente le stesse domande e risposte.
Per esempio:
$schema_faq[] = array(
'@type' => 'Question',
'name' => $domanda,
'acceptedAnswer' => array(
'@type' => 'Answer',
'text' => $risposta_schema,
),
);
Alla fine del ciclo viene creato lo schema:
$schema = array(
'@context' => 'https://schema.org',
'@type' => 'FAQPage',
'mainEntity' => $schema_faq,
);
e successivamente trasformato in JSON:
echo wp_json_encode(
$schema,
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
);
Il risultato sarà simile a:
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "Quanto costa il servizio?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Il servizio parte da 50 euro al mese."
}
}
]
}
Perché genero HTML e Schema.org dalla stessa sorgente
Una cosa che volevo assolutamente evitare era gestire separatamente:
- FAQ visualizzate;
- dati strutturati.
Una soluzione del genere sarebbe stata estremamente facile da mandare fuori sincronia.
Avrei potuto, per esempio, modificare una risposta nel frontend dimenticandomi di modificare anche il JSON-LD.
Utilizzando invece il Repeater ACF come unica fonte dei dati, questo problema non esiste.
La domanda utilizzata nell’accordion:
$faq['domanda']
è esattamente la stessa utilizzata nello schema.
Lo stesso vale per la risposta.
Una modifica effettuata dal backend WordPress aggiorna quindi contemporaneamente sia il contenuto della pagina sia il markup strutturato.
FAQPage e Google: attenzione alle informazioni SEO non più aggiornate
Qui è necessaria una precisazione importante.
Per molti anni l’implementazione di FAQPage è stata consigliata soprattutto per ottenere i classici FAQ rich result nelle SERP di Google.
La situazione oggi è cambiata.
Nel maggio 2026 Google ha annunciato la dismissione del FAQ rich result e dal 7 maggio 2026 questa funzionalità non viene più mostrata nei risultati di Google Search. Successivamente Google ha rimosso anche la relativa documentazione dedicata.
Questo non significa però che il vocabolario Schema.org sia scomparso.
FAQPage continua infatti a essere presente nella versione corrente di Schema.org come tipo specifico di WebPage.
Di conseguenza, nel 2026 non implementerei le FAQ promettendo al cliente:
“Così compariranno le domande direttamente nella SERP di Google.”
Non sarebbe corretto.
Continuo invece a considerare utile un markup strutturato coerente quando descrive realmente il contenuto della pagina, soprattutto perché rende la struttura semantica dei contenuti più esplicita e mantiene i dati in un formato standard interpretabile da sistemi automatici.
Il vero valore SEO delle FAQ rimane inoltre nel contenuto stesso: permettono di rispondere in modo chiaro a domande specifiche dell’utente e di approfondire aspetti che difficilmente troverebbero spazio nel corpo principale della pagina.
Attenzione ai plugin SEO che generano già Schema.org
Un altro controllo importante riguarda plugin come:
- Yoast SEO;
- Rank Math;
- altri plugin Schema;
- plugin specifici per FAQ.
Prima di generare manualmente FAQPage è opportuno verificare il sorgente della pagina.
Non vogliamo trovarci con due implementazioni indipendenti che descrivono le stesse FAQ.
Nel mio caso la logica è semplice: una sola sorgente dei dati e un solo componente responsabile dello schema FAQ.
Perché pulire la risposta prima di inserirla nel JSON-LD
La risposta ACF è un campo WYSIWYG e può quindi contenere HTML come:
<p>Testo della risposta</p>
<ul>
<li>Elemento uno</li>
<li>Elemento due</li>
</ul>
Per la visualizzazione frontend questo HTML va benissimo.
Per il valore utilizzato nel JSON-LD preferisco invece preparare una versione testuale:
$risposta_schema = wp_strip_all_tags(
strip_shortcodes($risposta)
);
Dopodiché normalizzo gli spazi:
$risposta_schema = trim(
preg_replace('/\s+/', ' ', $risposta_schema)
);
In questo modo ottengo un valore pulito e prevedibile.
Sicurezza dell’output
Nel codice utilizzo inoltre le normali funzioni di escaping WordPress.
Per le domande:
echo esc_html($domanda);
Per la risposta WYSIWYG:
echo wp_kses_post($risposta);
Questa distinzione è importante.
La domanda deve essere semplice testo, mentre nella risposta voglio mantenere l’HTML consentito da WordPress, come paragrafi, link ed eventualmente elenchi.
Come verificare il risultato
Dopo avere implementato il sistema conviene controllare tre elementi.
1. Verificare l’HTML
Aprendo il sorgente della pagina dovrebbero essere presenti elementi simili a:
<details class="faq-item">
con all’interno domanda e risposta.
2. Verificare il JSON-LD
Nel sorgente dovremmo trovare:
<script type="application/ld+json">
seguito dallo schema FAQPage.
3. Validare Schema.org
Il markup può essere controllato con lo Schema Markup Validator di Schema.org.
Dato che Google non supporta più il FAQ rich result nella Search, non bisogna aspettarsi che il vecchio Rich Results Test mostri la funzionalità FAQ come risultato avanzato disponibile.
Un ulteriore vantaggio: utilizzare il blocco nel template Oxygen
Questa soluzione diventa particolarmente comoda se il sito utilizza un template Oxygen comune per le pagine.
Il Code Block può infatti essere inserito una sola volta nel template.
La logica:
get_field('faq_attive')
deciderà cosa succede.
Se il campo è:
NO
non viene generato nulla.
Se invece è:
SÌ
compariranno automaticamente le FAQ definite nella pagina.
Il cliente può quindi gestire tutto senza entrare in Oxygen.
Perché preferisco questa soluzione
Alla fine ho scelto ACF + Oxygen + PHP perché mantiene una separazione abbastanza netta tra struttura e contenuti.
ACF gestisce i dati.
Il cliente inserisce domande e risposte.
Oxygen gestisce il layout.
Il componente viene posizionato nel punto corretto della pagina.
PHP gestisce la logica.
Decide quando mostrare il blocco e genera il markup.
Schema.org gestisce la rappresentazione strutturata.
I dati HTML e JSON-LD vengono generati a partire dalla stessa fonte.
Il risultato è molto più semplice da mantenere rispetto a una serie di FAQ create manualmente pagina per pagina.
Possibili evoluzioni
La stessa struttura può essere ulteriormente sviluppata.
Per esempio è possibile:
- associare un ID univoco a ogni FAQ;
- aprire automaticamente una FAQ tramite URL con anchor;
- aggiungere animazioni all’accordion;
- creare categorie di FAQ;
- riutilizzare FAQ globali in più pagine;
- aggiungere FAQ specifiche per Custom Post Type;
- creare FAQ multilingua con WPML;
- selezionare FAQ da un archivio centrale invece di riscriverle;
- generare altri tipi di dati strutturati a partire dai campi ACF.
Una possibile evoluzione interessante, soprattutto su siti molto grandi, consiste nel creare un vero Custom Post Type faq e permettere al redattore di selezionare quali domande mostrare in ogni pagina.
In questo modo la stessa risposta può essere mantenuta in un solo punto e riutilizzata su più URL.
Conclusioni
Integrare FAQ dinamiche in Oxygen Builder con Advanced Custom Fields richiede relativamente poco codice, ma permette di ottenere una struttura molto più gestibile.
Nel mio caso la soluzione ha permesso di raggiungere contemporaneamente quattro obiettivi:
- gestire le FAQ direttamente dal backend WordPress;
- mostrarle solamente nelle pagine che ne hanno bisogno;
- evitare di modificare ogni volta i template Oxygen;
- generare automaticamente il markup Schema.org partendo dagli stessi contenuti.
La parte che considero più importante non è però il singolo snippet PHP.
È l’architettura.
Quando contenuto visibile e metadati strutturati vengono generati dalla stessa fonte, diminuiscono drasticamente le possibilità che con il tempo le due informazioni diventino incoerenti.
Ed è precisamente il genere di automatizzazione che rende più semplice mantenere un sito WordPress anche mesi o anni dopo la sua realizzazione.
Ti interessa migliorare il tuo sito? Contattami
Domande frequenti
Per la soluzione descritta sì, perché viene utilizzato il campo Repeater di Advanced Custom Fields. È possibile ottenere un risultato simile anche con altre strutture, ma il Repeater rende particolarmente semplice aggiungere e ordinare un numero variabile di FAQ.
Sì. È sufficiente modificare le regole location del gruppo ACF aggiungendo anche il post type post.
Sì. Basta assegnare il gruppo ACF al relativo post type e utilizzare il Code Block all’interno del template Oxygen che lo visualizza.
No. Se le pagine utilizzano un template comune, il blocco può essere aggiunto direttamente al template. Il campo faq_attive stabilisce poi in quali pagine deve essere visualizzato.
No. Google ha dismesso il FAQ rich result nel maggio 2026. Lo schema FAQPage continua però a far parte del vocabolario Schema.org e può ancora essere utilizzato per descrivere semanticamente una pagina contenente domande frequenti.
È buona pratica mantenere perfettamente coerente il markup strutturato con il contenuto reale della pagina. La soluzione illustrata nell’articolo risolve il problema alla radice perché utilizza gli stessi campi ACF sia per il contenuto HTML sia per il JSON-LD.
Sì, ma è opportuno verificare che il plugin SEO non stia già producendo uno schema relativo alle stesse FAQ. L’obiettivo è evitare markup duplicato o strutture indipendenti che potrebbero diventare incoerenti.