Perché implementare JSON-LD manualmente
I plugin SEO come Yoast o RankMath aggiungono JSON-LD automaticamente, ma con tre problemi ricorrenti:
- Output generico che non sfrutta metadati custom specifici dei tuoi progetti
- Overhead di codice e query database non necessarie se serve solo structured data
- Conflitti quando gestisci più siti client con requisiti diversi
L’implementazione manuale via wp_head o wp_footer ti dà controllo totale con ~50 righe di codice nel functions.php del tema child o in un mu-plugin dedicato.
Secondo i dati di HTTPArchive (gennaio 2026), il 73% dei siti WordPress usa ancora plugin per structured data, ma le agenzie che gestiscono fleet di siti stanno migrando verso soluzioni custom per ridurre dipendenze e migliorare performance.
Struttura base: Organization e WebSite
Ogni sito dovrebbe dichiarare l’entità principale (Organization o Person) e il WebSite con search action. Questo codice va nel functions.php:
<?php
function agencypilot_schema_organization() {
if ( ! is_front_page() ) return;
$schema = [
'@context' => 'https://schema.org',
'@graph' => [
[
'@type' => 'Organization',
'@id' => home_url( '/#organization' ),
'name' => get_bloginfo( 'name' ),
'url' => home_url( '/' ),
'logo' => [
'@type' => 'ImageObject',
'url' => get_theme_mod( 'custom_logo' )
? wp_get_attachment_image_url( get_theme_mod( 'custom_logo' ), 'full' )
: '',
],
'sameAs' => [
get_option( 'social_facebook', '' ),
get_option( 'social_linkedin', '' ),
]
],
[
'@type' => 'WebSite',
'@id' => home_url( '/#website' ),
'url' => home_url( '/' ),
'name' => get_bloginfo( 'name' ),
'publisher' => [
'@id' => home_url( '/#organization' )
],
'potentialAction' => [
'@type' => 'SearchAction',
'target' => home_url( '/?s={search_term_string}' ),
'query-input' => 'required name=search_term_string'
]
]
]
];
echo '<script type="application/ld+json">' . wp_json_encode( $schema, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) . '</script>';
}
add_action( 'wp_head', 'agencypilot_schema_organization' );
?>
Note tecniche:
- Usa
@graphper collegare entità multiple in un unico blocco JSON-LD @idcon fragment (#organization) crea riferimenti riutilizzabiliJSON_UNESCAPED_SLASHESevita escape non necessari degli URL- Filtra i social con
array_filter()se vuoi rimuovere valori vuoti
Article Schema per post e custom post type
Per post singoli, implementa Article (o varianti come BlogPosting, NewsArticle). Questo codice gestisce autore, immagine featured e date:
<?php
function agencypilot_schema_article() {
if ( ! is_singular( ['post', 'case-study'] ) ) return;
$post_id = get_the_ID();
$author_id = get_post_field( 'post_author', $post_id );
$schema = [
'@context' => 'https://schema.org',
'@type' => 'Article',
'@id' => get_permalink() . '#article',
'headline' => get_the_title(),
'description' => get_the_excerpt(),
'image' => has_post_thumbnail() ? get_the_post_thumbnail_url( $post_id, 'full' ) : '',
'datePublished' => get_the_date( 'c', $post_id ),
'dateModified' => get_the_modified_date( 'c', $post_id ),
'author' => [
'@type' => 'Person',
'name' => get_the_author_meta( 'display_name', $author_id ),
'url' => get_author_posts_url( $author_id )
],
'publisher' => [
'@id' => home_url( '/#organization' )
],
'mainEntityOfPage' => [
'@type' => 'WebPage',
'@id' => get_permalink()
]
];
echo '<script type="application/ld+json">' . wp_json_encode( $schema, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) . '</script>';
}
add_action( 'wp_head', 'agencypilot_schema_article' );
?>
Dettagli implementativi:
- Usa formato ISO 8601 (
'c') per le date, richiesto da Google - Collega
publisherall’Organization tramite@idreference - Per NewsArticle aggiungi campi
articleSectionewordCount - Valida sempre con Rich Results Test di Google
BreadcrumbList per navigazione gerarchica
Le breadcrumb aiutano Google a capire la struttura del sito. Implementazione per post con categoria singola:
<?php
function agencypilot_schema_breadcrumb() {
if ( is_front_page() ) return;
$items = [[
'@type' => 'ListItem',
'position' => 1,
'name' => 'Home',
'item' => home_url( '/' )
]];
$position = 2;
if ( is_singular( 'post' ) ) {
$category = get_the_category()[0];
$items[] = [
'@type' => 'ListItem',
'position' => $position++,
'name' => $category->name,
'item' => get_category_link( $category->term_id )
];
}
$items[] = [
'@type' => 'ListItem',
'position' => $position,
'name' => get_the_title(),
'item' => get_permalink()
];
$schema = [
'@context' => 'https://schema.org',
'@type' => 'BreadcrumbList',
'itemListElement' => $items
];
echo '<script type="application/ld+json">' . wp_json_encode( $schema, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) . '</script>';
}
add_action( 'wp_head', 'agencypilot_schema_breadcrumb' );
?>
Estensioni comuni:
- Per custom post type con tassonomie custom, sostituisci
get_the_category()conget_the_terms() - Per e-commerce WooCommerce, aggiungi categoria prodotto e shop base
- Gestisci gerarchie categoria multiple scegliendo la primary (Yoast) o la prima
FAQ Schema per contenuti Q&A
Se usi blocchi Gutenberg o ACF Repeater per FAQ, questo codice genera FAQPage schema automaticamente:
<?php
function agencypilot_schema_faq() {
if ( ! is_singular() ) return;
// Esempio con ACF Repeater
$faqs = get_field( 'faq_items' ); // [{"question": "", "answer": ""}]
if ( ! $faqs ) return;
$questions = [];
foreach ( $faqs as $faq ) {
$questions[] = [
'@type' => 'Question',
'name' => $faq['question'],
'acceptedAnswer' => [
'@type' => 'Answer',
'text' => wp_strip_all_tags( $faq['answer'] )
]
];
}
$schema = [
'@context' => 'https://schema.org',
'@type' => 'FAQPage',
'mainEntity' => $questions
];
echo '<script type="application/ld+json">' . wp_json_encode( $schema, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) . '</script>';
}
add_action( 'wp_head', 'agencypilot_schema_faq' );
?>
Considerazioni:
- Google mostra FAQ rich snippet solo per contenuti effettivamente visibili nella pagina
- Usa
wp_strip_all_tags()per rimuovere HTML dalla risposta, oppure converti in plain text - Massimo 1 FAQPage schema per pagina, non combinare con QAPage
Performance e testing in ambiente multi-client
Quando gestisci JSON-LD per decine di siti client:
- Centralizza in mu-plugin: crea
wp-content/mu-plugins/agency-schema.phpcon configurazione per site_id - Usa transient cache: per Organization schema che non cambia, usa
set_transient()con durata 1 mese - Hook priority: esegui dopo altri plugin SEO con
add_action('wp_head', 'function', 99) - Conditional loading: evita output su pagine non indicizzabili (login, admin preview)
Esempio di verifica automatizzata con WP-CLI custom command:
wp eval "echo json_encode(agencypilot_schema_organization_data());" | jq
Per monitoraggio continuo su fleet di siti, puoi integrare validazione Schema.org nell’uptime check di AgencyPilot, verificando presence e validità del JSON-LD a ogni scan.
Checklist implementazione completa
Prima di considerare l’implementazione production-ready:
- Valida ogni tipo di schema con Google Rich Results Test e Schema.org validator
- Verifica che non ci siano duplicati JSON-LD (disabilita output da plugin attivi)
- Testa con
view-source:che il JSON sia valido (niente caratteri escaped male) - Controlla Search Console dopo 2-3 settimane per errori Enhancements
- Documenta quale schema è attivo su quali post type per il team
I rich snippet non sono ranking factor diretto, ma il CTR aumenta mediamente del 15-30% secondo studi Backlinko 2025, quindi l’impatto su traffico organico è misurabile.
FAQ
Devo usare @graph o singoli script JSON-LD separati?
@graph è preferibile quando hai entità collegate (Organization + WebSite, Article + BreadcrumbList) perché permette di usare @id reference evitando duplicazioni. Per schema indipendenti come FAQ puoi usare script separati, Google li processa comunque correttamente.
Come gestire JSON-LD multilingua con WPML/Polylang?
Avvolgi le funzioni con check if (function_exists('pll_current_language')) e usa pll_home_url() invece di home_url(). Per Organization sameAs, crea array di social per lingua usando option con suffisso lingua (get_option('social_facebook_' . pll_current_language())).
Meglio wp_head o wp_footer per JSON-LD?
Google legge JSON-LD ovunque nel DOM, ma wp_head è standard e consigliato. Usa wp_footer solo se hai conflitti con altri script o vuoi priorità bassa. Evita di splittare: metti tutto in head per consistenza e facilità debug.
Come validare JSON-LD in staging prima del deploy?
Usa ngrok o LocalWP Live Link per esporre staging a Google Rich Results Test, oppure copia view-source del JSON e incollalo nel Schema.org validator. Per CI/CD, integra schema-validator npm package nei test automatici pre-deploy.
I transient cache di Schema.org possono causare contenuto stale?
Sì, se usi transient per Article schema con date modificate. Usa cache solo per dati statici (Organization, WebSite). Per contenuti dinamici, implementa invalidazione cache con hook save_post che fa delete_transient('schema_article_' . $post_id).