Come creare FAQ dinamiche in Oxygen Builder con ACF e dati strutturati Schema.org

Come Creare Faq Dinamiche In Oxygen Builder Con Acf E Dati Strutturati Schema.org

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.

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:

  • domanda
  • risposta

La struttura completa è:

EtichettaNome campoTipo
Mostra FAQfaq_attiveTrue / False
Titolo sezionefaq_titoloText
FAQfaqRepeater
DomandadomandaText
RispostarispostaWYSIWYG

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:

  1. la visualizzazione delle FAQ;
  2. 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:

&lt;details>
    &lt;summary>Domanda&lt;/summary>

    &lt;div>
        Risposta
    &lt;/div>
&lt;/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:

&lt;p>Testo della risposta&lt;/p>
&lt;ul>
    &lt;li>Elemento uno&lt;/li>
    &lt;li>Elemento due&lt;/li>
&lt;/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:

&lt;details class="faq-item">

con all’interno domanda e risposta.

2. Verificare il JSON-LD

Nel sorgente dovremmo trovare:

&lt;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

È necessario ACF Pro per creare questo sistema?

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.

Posso utilizzare le FAQ anche negli articoli WordPress?

Sì. È sufficiente modificare le regole location del gruppo ACF aggiungendo anche il post type post.

Posso utilizzare lo stesso sistema con un Custom Post Type?

Sì. Basta assegnare il gruppo ACF al relativo post type e utilizzare il Code Block all’interno del template Oxygen che lo visualizza.

Devo aggiungere il Code Block Oxygen in ogni pagina?

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.

Le FAQ con FAQPage compaiono ancora come rich result su Google?

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.

Le FAQ devono essere visibili anche nella pagina?

È 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.

Posso usare Yoast o Rank Math insieme a questa soluzione?

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.

Richiedi informazioni

Raccontami il tuo progetto, tracking, accessibilità, campagne adv da sistemare o semplicemente un sito che non vuol saperne di funzionare. Insieme possiamo migliorarlo.

    Accetto la Privacy Policy
    Autorizzo al trattamento dei miei dati personali, per ricevere le informazioni richieste attraverso questo modulo di contatto. I dati da te inseriti attraverso questo modulo verranno utilizzati solo per essere da me ricontattato.

    Recensioni

    Pubblicato su Google Google
    Flavio Corò profile picture
    Flavio Corò
    22/09/2023
    Google star 1Google star 2Google star 3Google star 4Google star 5Trustindex verifica che la fonte originale della recensione sia Google.
    Ho avuto il piacere di contattare la Sig.ra Sara tramite suggerimento da parte di un mio cliente per riisolvere una pratica burocratica/lavorativa e devo dire che la VELOCITÀ, PROFESSIONALITÀ, e COMPETENZA fanno parte del Suo bagaglio lavorativo,qualita al giorno d'oggi per niente scontate.Pienamente soddisfatto.
    Pubblicato su Google Google
    Sonico Beauty profile picture
    Sonico Beauty
    17/01/2023
    Google star 1Google star 2Google star 3Google star 4Google star 5Trustindex verifica che la fonte originale della recensione sia Google.
    Sara ci ha aiutato a risolvere piu' problemi in pochi minuti e con molta professionalità. Massima disponibilità e grande conoscenza nel mondo web. Assolutamente consigliata. Grazie mille
    Pubblicato su Google Google
    Studio Legale Avvocato Armando Baffioni Venturi profile picture
    Studio Legale Avvocato Armando Baffioni Venturi
    07/11/2022
    Google star 1Google star 2Google star 3Google star 4Google star 5Trustindex verifica che la fonte originale della recensione sia Google.
    Contattata tramite il suo sito web si è rivelata una persona affabile e preparata, pronta nel recepire le mie necessità e nel darmi le indicazioni corrette da seguire. Assolutamente da consigliare anche per assistenze e consulenze da remoto.
    Pubblicato su Google Google
    Hermann Gils profile picture
    Hermann Gils
    28/10/2022
    Google star 1Google star 2Google star 3Google star 4Google star 5Trustindex verifica che la fonte originale della recensione sia Google.
    Molto esperta, preparata e competente!
    Pubblicato su Google Google
    Antonio Cimadomo profile picture
    Antonio Cimadomo
    07/08/2022
    Google star 1Google star 2Google star 3Google star 4Google star 5Trustindex verifica che la fonte originale della recensione sia Google.
    Definire La Sig.a Sara Gasparini persona capace, Umile, molto intelligente e disponibile è il minimo che si possa fare. E' stata una grande emozione per me averla 'scovata'. Persone come Sara alzano, e di tanto, il livello sociale/culturale. Ringrazio ancora Sara per esserci.... Antonio p.s. Peccato ci siano solo 5 stelle :-(
    Pubblicato su Google Google
    Max R_DJ profile picture
    Max R_DJ
    16/06/2021
    Google star 1Google star 2Google star 3Google star 4Google star 5Trustindex verifica che la fonte originale della recensione sia Google.
    Molto professionale e preparata, ha risolto subito un mio problema inerente alle recensioni Google sul mio sito web. Complimenti Sara!
    Pubblicato su Google Google
    Fabrizio Aureli profile picture
    Fabrizio Aureli
    18/03/2021
    Google star 1Google star 2Google star 3Google star 4Google star 5Trustindex verifica che la fonte originale della recensione sia Google.
    Ottima collaboratrice

    Scrivimi una recensione

    Questo QR Code ti permette di lasciarmi una recensione in maniera facile e veloce.

    Altrimenti usa il bottone qui sotto.

    Lascia una recensione su Google