Skip to main content

Topic, Target e Stage nel dataLayer: misurare con le coordinate del content model

Un report che elenca pagine e numeri ti dice cosa è successo a ciascuna URL, non di cosa parla il sito, per chi e a che punto del percorso le persone si fermano. Se i tuoi contenuti sono già classificati per Topic, Target e Stage, quella classificazione può arrivare in GA4 così com’è. In questo tutorial la portiamo nel dataLayer, la leggiamo con Google Tag Manager e la registriamo come dimensioni personalizzate.

In questo articolo parlo di:Google Tag Manager, Bebop Content Model
Questo articolo è scritto pensando a:Data Analyst, Developers

Disegna la struttura prima del codice

Step

Il dataLayer è un contratto: da una parte il sito promette dei nomi, dall’altra GTM ci costruisce sopra variabili, tag e report. Se cambi un nome fra sei mesi, si rompono in silenzio i report di sei mesi. Quindi la struttura si decide prima, e una volta sola.

Quella che uso ha una sola chiave di primo livello, bcm, con due rami:

{
  "bcm": {
    "identity": {
      "type": "tutorial",
      "view": "single",
      "content_id": "post_42"
    },
    "classification": {
      "topic": { "primary": "data-analisi", "cluster": "data-analisi-gtm", "all": ["data-analisi", "bebop-content-model"] },
      "target": { "primary": "professionisti-del-digitale", "end_user": "professionisti-del-digitale-data-analyst", "all": ["professionisti-del-digitale"] },
      "cj": { "primary": "reader-journey", "cj_stage": "02 - Approfondimento", "all": ["reader-journey"] },
      "intent": "Portare topic, target e stage nel dataLayer in una struttura stabile"
    }
  }
}

Tre scelte da notare. Una chiave sola, bcm, perché quei dati appartengono al modello e non si mescolano con quelli del tema o di altri plugin. identity c’è su ogni pagina, anche su una 404; classification solo dove il contenuto è classificato. E i valori sono slug, non etichette: se domani rinomini “SEO Tecnica” in “SEO tecnica e AEO”, lo slug resta lo stesso e i report non si spezzano.

Ricava l'identità della pagina

Step

L’identità non si dichiara: si deriva dal contesto. Il Type è il post type, la View è la forma della pagina (singolo, archivio, ricerca, 404), il content_id un identificativo stabile.

Crea un file bcm-datalayer.php in wp-content/mu-plugins/ e parti da qui:

<?php
/**
 * Plugin Name: BCM dataLayer – le coordinate del content model per GTM
 * Description: Spinge nel dataLayer l'identità della pagina e la sua classificazione (Topic, Target, Stage).
 */

if (!defined('ABSPATH')) {
  exit;
}

function bcm_dl_identity(): array {
  if (is_singular()) {
    $post_id = get_queried_object_id();

    return [
      'type' => get_post_type($post_id),
      'view' => 'single',
      'content_id' => 'post_' . $post_id,
    ];
  }

  if (is_home()) {
    return ['type' => 'blog', 'view' => 'archive', 'content_id' => 'blog'];
  }

  if (is_post_type_archive()) {
    $post_type = get_query_var('post_type');
    $post_type = is_array($post_type) ? reset($post_type) : $post_type;

    return ['type' => $post_type, 'view' => 'archive', 'content_id' => 'archive_' . $post_type];
  }

  if (is_search()) {
    return ['type' => 'search', 'view' => 'search_result', 'content_id' => 'search'];
  }

  if (is_404()) {
    return ['type' => '404', 'view' => 'not_found', 'content_id' => '404'];
  }

  return ['type' => 'other', 'view' => 'other', 'content_id' => 'other'];
}

Leggi le coordinate dalle tassonomie

Step

Leggi i termini assegnati nell’ordine in cui l’autore li ha scelti: il primo è il Primary.

function bcm_dl_terms(int $post_id, string $taxonomy): array {
  $terms = wp_get_object_terms($post_id, $taxonomy, ['orderby' => 'term_order']);

  return is_array($terms) ? $terms : [];
}
Tip

L’ordine di assegnazione si conserva solo se la tassonomia è registrata con sort => true. Senza, term_order non dice niente e il “primo” diventa casuale.

Step

Risali dalla foglia alla radice: un Cluster appartiene a un Topic, un End User a una Persona, uno Stage a una Journey. Con due livelli al massimo basta un passo.

function bcm_dl_root(WP_Term $term): WP_Term {
  if (!$term->parent) {
    return $term;
  }

  $root = get_term($term->parent, $term->taxonomy);

  return $root instanceof WP_Term ? $root : $term;
}
Step

Per ogni dimensione ricava la radice del Primary, l’eventuale figlio e l’elenco delle radici di tutti i termini assegnati:

function bcm_dl_dimension(int $post_id, string $taxonomy): ?array {
  $terms = bcm_dl_terms($post_id, $taxonomy);
  if (!$terms) {
    return null;
  }

  $primary = $terms[0];
  $root = bcm_dl_root($primary);

  $all = [];
  foreach ($terms as $term) {
    $all[] = bcm_dl_root($term)->slug;
  }

  return [
    'root' => $root,
    'child' => $primary->term_id !== $root->term_id ? $primary : null,
    'all' => array_values(array_unique($all)),
  ];
}

Dai agli Stage un ordine

Step

Aggiungi allo Stage un prefisso con la sua posizione tra i fratelli: 01 - Scoperta, 02 - Approfondimento, 03 - Riferimento.

function bcm_dl_stage_label(WP_Term $stage): string {
  $siblings = get_terms([
    'taxonomy' => $stage->taxonomy,
    'parent' => $stage->parent,
    'hide_empty' => false,
    'orderby' => 'term_id',
    'order' => 'ASC',
    'fields' => 'ids',
  ]);

  $ids = is_array($siblings) ? array_map('intval', $siblings) : [];
  $position = array_search($stage->term_id, $ids, true);
  $number = $position === false ? 0 : $position + 1;

  return sprintf('%02d - %s', $number, $stage->name);
}
Tip

Senza prefisso, in Looker Studio le tappe si ordinano in ordine alfabetico: Approfondimento, Riferimento, Scoperta. Il percorso letto al contrario. Con il prefisso si ordinano da sole, senza un campo calcolato in ogni report.

Tip

Qui la posizione segue l’ordine di creazione dei termini (term_id). Se crei gli Stage in un ordine diverso da quello del percorso, sostituisci l’ordinamento con un meta di termine dedicato.

Componi il push e stampalo prima del container

Step

Ora metti insieme identità e classificazione e stampa il push nell’<head>, prima dello snippet di Google Tag Manager. La documentazione di Google è esplicita: se vuoi che le variabili siano disponibili quando il container si carica, il dataLayer.push() va sopra il codice del container.

function bcm_dl_classification(int $post_id): ?array {
  $topic = bcm_dl_dimension($post_id, 'bcm_topic');
  $target = bcm_dl_dimension($post_id, 'bcm_target');
  $journey = bcm_dl_dimension($post_id, 'bcm_journey');

  if (!$topic && !$target && !$journey) {
    return null;
  }

  $intent = trim((string) get_post_meta($post_id, 'bcm_intento', true));

  return [
    'topic' => [
      'primary' => $topic ? $topic['root']->slug : null,
      'cluster' => $topic && $topic['child'] ? $topic['child']->slug : null,
      'all' => $topic ? $topic['all'] : [],
    ],
    'target' => [
      'primary' => $target ? $target['root']->slug : null,
      'end_user' => $target && $target['child'] ? $target['child']->slug : null,
      'all' => $target ? $target['all'] : [],
    ],
    'cj' => [
      'primary' => $journey ? $journey['root']->slug : null,
      'cj_stage' => $journey && $journey['child'] ? bcm_dl_stage_label($journey['child']) : null,
      'all' => $journey ? $journey['all'] : [],
    ],
    'intent' => $intent,
  ];
}

function bcm_dl_payload(): array {
  $payload = ['identity' => bcm_dl_identity()];

  if (is_singular()) {
    $classification = bcm_dl_classification(get_queried_object_id());
    if ($classification) {
      $payload['classification'] = $classification;
    }
  }

  return $payload;
}

add_action('wp_head', 'bcm_dl_print', 1);

function bcm_dl_print(): void {
  $json = wp_json_encode(['bcm' => bcm_dl_payload()]);
  if (!$json) {
    return;
  }

  echo "<script>\n";
  echo "window.dataLayer = window.dataLayer || [];\n";
  echo 'window.dataLayer.push(' . $json . ");\n";
  echo "</script>\n";
}

La priorità 1 su wp_head serve proprio a uscire prima del container. Se lo snippet di GTM lo stampa un altro plugin, controlla con quale priorità lo fa.

Leggi le coordinate in Google Tag Manager

Step

In GTM crea una variabile di tipo Variabile livello dati per ogni coordinata che vuoi misurare, con Versione 2:

Variabile GTMNome della variabile livello dati
BCM · Typebcm.identity.type
BCM · Viewbcm.identity.view
BCM · Topicbcm.classification.topic.primary
BCM · Clusterbcm.classification.topic.cluster
BCM · Personabcm.classification.target.primary
BCM · End Userbcm.classification.target.end_user
BCM · Stagebcm.classification.cj.cj_stage
Tip

La Versione 2 interpreta i punti come livelli annidati: bcm.classification.topic.primary entra nell’oggetto. La Versione 1 cercherebbe una chiave che si chiama letteralmente così, e restituirebbe undefined.

Tip

Su una pagina non classificata classification non esiste: le variabili restano vuote, ed è giusto così. Meglio un vuoto onesto che un “non classificato” scritto a mano in ogni tag.

Mandale a GA4 e registra le dimensioni

Step

Aggiungi le variabili come parametri evento ai tag GA4 che ti interessano, a partire dal page_view. Se usi una variabile di impostazioni evento condivisa, mettile lì una volta sola. Dai ai parametri nomi brevi e parlanti: post_type, view, topic, cluster, persona, end_user, stage.

Step

In GA4, in Amministrazione → Definizioni personalizzate, crea una dimensione personalizzata basata sugli eventi per ogni parametro, con lo stesso nome del parametro.

Tip

Conta i limiti prima di partire. Per una proprietà standard: 25 parametri per evento, nomi dei parametri fino a 40 caratteri, valori fino a 100 caratteri, 50 dimensioni personalizzate basate sugli eventi. Sette coordinate stanno larghe; tutto quello che aggiungi dopo pesa sullo stesso budget.

Tip

Le nuove dimensioni compaiono nei report dopo 24–48 ore, e solo per i dati raccolti da quando esistono: registrale subito, anche prima di averne bisogno.

Tip

L’intento non va a GA4. Il suo lavoro principale non è nella misura: serve a chi scrive, per capire che finalità dare al testo. Nel dataLayer può restare, se un giorno ti serve per un’analisi mirata; ma è testo libero, diverso per ogni contenuto, e Google sconsiglia le dimensioni ad alta cardinalità, che finiscono nella riga “(other)”. Per questo nella tabella del passo 6 non c’è.

Verifica

  1. Apri il sorgente della pagina. Nel <head> devi trovare lo script con il dataLayer.push({"bcm": …}), sopra lo snippet di Google Tag Manager. Su un contenuto classificato deve esserci classification; su un archivio o una ricerca solo identity.
  2. Usa l’anteprima di Tag Manager (Tag Assistant). Nella scheda del livello dati controlla l’oggetto bcm; nella scheda delle variabili controlla che BCM · Topic, BCM · Stage e le altre abbiano il valore atteso già all’evento di caricamento del container.
  3. Guarda la DebugView di GA4. Sul page_view devono comparire i parametri topic, stage e gli altri, con i valori della pagina.
  4. Dopo 24–48 ore, apri un’esplorazione con le nuove dimensioni: per esempio pagine viste per stage e topic. È la prima volta che il sito si legge per coordinate e non per URL.

Varianti ed errori comuni

Varianti

  • Archivi filtrati per Topic. Su un archivio come /…/blog/ sotto una pillar il contenuto non c’è, ma il Topic sì: puoi aggiungere a classification il solo Topic dell’archivio, letto dall’URL.
  • Click, referrer, landing. Il dataLayer descrive solo la pagina corrente. Per sapere verso quale Topic porta un link, o da quale Stage arriva una sessione, serve una mappa dalle URL alle coordinate. È il passo successivo, e merita un pezzo a sé.
  • page_title e page_location leggibili. Se li mandi tu, usa il titolo del contenuto e non il titolo SEO, che Yoast può riscrivere per il brand o per la keyword, e un URL senza querystring.

Errori comuni

  • Push dopo il container. Il messaggio viene messo in coda ed elaborato dopo gli altri: il page_view parte senza coordinate.
  • Variabile livello dati in Versione 1 con un nome puntato: restituisce undefined anche se il dato c’è.
  • Stage senza prefisso: il percorso letto in ordine alfabetico.
  • Dimensioni che duplicano quelle predefinite. Registrare page_title o page_location come dimensioni personalizzate consuma la quota senza darti niente di nuovo: GA4 le ha già.
  • Primary casuale. Tassonomia registrata senza sort => true: il “primo termine” non è quello scelto dall’autore.
  • L’intento come dimensione personalizzata. Una riga per contenuto, quasi tutte con un solo valore: un report che non riassume niente e consuma una delle 50 dimensioni.

Cosa mi porto via

Tutto questo modello è nato da qui, da una domanda da data analyst. Era novembre 2025 e davanti ai report dei progetti mi chiedevo che senso avesse guardare tanti dati legati a tante URL, se quelle URL non erano raggruppate per concetto. Il dataLayer è il punto in cui quella domanda trova risposta: la pagina non arriva più in GA4 come un indirizzo, ma come un punto con delle coordinate.

Non installo un tag: disegno il perimetro di quello che voglio misurare. Un numero che non risponde a una domanda non è informazione, è rumore, e il modo più semplice per non raccogliere rumore è decidere prima quali domande farò ai dati: di cosa parla questo contenuto, per chi è, a che punto del percorso sta.

Su questo sito ho iniziato a raccogliere i dati proprio così: presto ti mostro come li leggo in una dashboard.

E tu, i tuoi report rispondono a una domanda o elencano pagine?

Con questo pezzo la serie ha mostrato la catena intera: la classificazione nelle tassonomie, lo stesso dato nel grafo schema.org, lo stesso dato nella misura. Se vuoi ripercorrerla dall’inizio, parti dal primo articolo e poi dal tutorial su about e audience. Se devi convincere chi coordina che misurare per coordinate vale lo sforzo: Il problema non sono i team: è la lingua.

© 2026 Angelo Giovanni Dore • Contenuti rilasciati con licenza CC BY 4.0
I contenuti hanno finalità divulgative e riflettono esperienze e opinioni personali.
Pur cercando di mantenerli accurati e aggiornati, non costituiscono consulenza professionale.