back to top

WordPress: Custom Post Type e tassonomie senza plugin

Sei uno sviluppatore WordPress che vuole davvero padroneggiare la piattaforma? Vuoi creare strutture dati complesse e personalizzate senza appesantire il tuo sito con plugin esterni? Allora sei nel posto giusto. In questo articolo approfondiremo come creare WordPress Custom Post Type (CPT) e tassonomie personalizzate direttamente via codice PHP.

Imparare a gestire CPT e tassonomie manualmente ti darà un controllo senza precedenti sulla struttura del tuo sito, migliorando performance, manutenibilità e flessibilità. Abbandonare l’approccio basato esclusivamente sui plugin per queste funzionalità core è un passo fondamentale per diventare uno sviluppatore WordPress avanzato.

Cosa sono i Custom Post Type (CPT) e le Tassonomie?

WordPress, di base, offre alcuni tipi di contenuto predefiniti: Articoli (post), Pagine (page), Allegati (attachment), Revisioni (revision) e Menu (nav_menu_item). Questi sono sufficienti per blog o siti semplici. Ma cosa succede se devi gestire contenuti specifici come “Progetti”, “Eventi”, “Prodotti”, “Ricette” o “Membri del Team”?

Qui entrano in gioco i WordPress Custom Post Type. Sono, in essenza, nuovi tipi di contenuto che puoi definire tu stesso, con campi, funzionalità e template di visualizzazione dedicati. Permettono di separare logicamente e gestire in modo più organizzato contenuti diversi dagli articoli standard.

Le Tassonomie, invece, sono sistemi per classificare e raggruppare i contenuti. WordPress include due tassonomie predefinite: Categorie (category, gerarchiche) e Tag (post_tag, non gerarchiche). Puoi creare tassonomie personalizzate per organizzare i tuoi CPT (ma anche i post standard) in modi più specifici. Ad esempio, per un CPT “Progetti”, potresti volere tassonomie come “Tipologia Progetto” (gerarchica: Web, Mobile, Grafica) e “Tecnologie Utilizzate” (non gerarchica: PHP, React, WordPress, Figma).

Perché Creare CPT e Tassonomie via Codice?

Esistono ottimi plugin (come ACF, CPT UI, Pods) che permettono di creare CPT e tassonomie tramite un’interfaccia grafica. Allora perché farlo via codice?

  1. Performance: Ogni plugin attivo aggiunge un certo overhead al caricamento di WordPress. Creare CPT/tassonomie via codice significa eseguire solo il codice strettamente necessario, senza funzionalità extra che potresti non usare, risultando in un sito più leggero e veloce. Meno codice significa meno query al database e un TTI (Time To Interactive) potenzialmente migliore. Approfondisci l’importanza delle performance in Web Performance 2025: introduzione ai Core Web Vitals e ottimizzazioni chiave.
  2. Controllo Totale: Il codice ti dà il controllo completo su ogni singolo aspetto della registrazione del CPT e della tassonomia. Puoi definire capacità specifiche, rewrite rules complesse, etichette personalizzate e integrazioni avanzate che i plugin potrebbero non permettere o rendere complicate.
  3. Leggerezza e Manutenibilità: Evitare plugin per funzionalità strutturali come queste riduce le dipendenze esterne. Meno plugin significa meno aggiornamenti da gestire, meno potenziali conflitti e una codebase più snella e facile da manutenere nel tempo, specialmente se lavori in team (vedi Git & GitHub per team frontend: guida definitiva).
  4. Comprensione Profonda: Scrivere il codice ti costringe a capire come WordPress funziona internamente, migliorando le tue competenze generali sulla piattaforma. È un passo cruciale nella transizione da utente a sviluppatore WordPress avanzato.
  5. Sicurezza: Meno codice di terze parti significa una superficie di attacco potenzialmente ridotta. Hai il controllo diretto sul codice eseguito.

Certo, l’approccio via codice richiede familiarità con PHP e le API di WordPress, ma i benefici a lungo termine, specialmente per progetti complessi o custom, sono innegabili.

Come creare un Custom Post Type

La creazione di un WordPress Custom Post Type avviene tramite la funzione register_post_type(). Questa funzione va agganciata all’action hook init, che si attiva dopo che WordPress ha completato il caricamento ma prima che vengano inviati gli header.

Il posto migliore per inserire questo codice non è direttamente nel functions.php del tema (perché cambieresti tema?), ma piuttosto in un plugin personalizzato specifico per il sito (site-specific plugin) o in un Must-Use plugin (mu-plugin). Per semplicità, negli esempi useremo l’approccio del functions.php, ma tieni a mente questa best practice.

Esempio Pratico: Creiamo il CPT “Progetti”

<?php
/**
 * Registra il Custom Post Type "Progetti".
 * Agganciato all'hook 'init'.
 */
function cyba_register_project_post_type() {

    $labels = array(
        'name'                  => _x( 'Progetti', 'Post Type General Name', 'cyberalchimista' ),
        'singular_name'         => _x( 'Progetto', 'Post Type Singular Name', 'cyberalchimista' ),
        'menu_name'             => __( 'Progetti', 'cyberalchimista' ),
        'name_admin_bar'        => __( 'Progetto', 'cyberalchimista' ),
        'archives'              => __( 'Archivio Progetti', 'cyberalchimista' ),
        'attributes'            => __( 'Attributi Progetto', 'cyberalchimista' ),
        'parent_item_colon'     => __( 'Progetto Genitore:', 'cyberalchimista' ),
        'all_items'             => __( 'Tutti i Progetti', 'cyberalchimista' ),
        'add_new_item'          => __( 'Aggiungi Nuovo Progetto', 'cyberalchimista' ),
        'add_new'               => __( 'Aggiungi Nuovo', 'cyberalchimista' ),
        'new_item'              => __( 'Nuovo Progetto', 'cyberalchimista' ),
        'edit_item'             => __( 'Modifica Progetto', 'cyberalchimista' ),
        'update_item'           => __( 'Aggiorna Progetto', 'cyberalchimista' ),
        'view_item'             => __( 'Visualizza Progetto', 'cyberalchimista' ),
        'view_items'            => __( 'Visualizza Progetti', 'cyberalchimista' ),
        'search_items'          => __( 'Cerca Progetto', 'cyberalchimista' ),
        'not_found'             => __( 'Nessun progetto trovato', 'cyberalchimista' ),
        'not_found_in_trash'    => __( 'Nessun progetto trovato nel cestino', 'cyberalchimista' ),
        'featured_image'        => __( 'Immagine in Evidenza', 'cyberalchimista' ),
        'set_featured_image'    => __( 'Imposta immagine in evidenza', 'cyberalchimista' ),
        'remove_featured_image' => __( 'Rimuovi immagine in evidenza', 'cyberalchimista' ),
        'use_featured_image'    => __( 'Usa come immagine in evidenza', 'cyberalchimista' ),
        'insert_into_item'      => __( 'Inserisci nel progetto', 'cyberalchimista' ),
        'uploaded_to_this_item' => __( 'Caricato su questo progetto', 'cyberalchimista' ),
        'items_list'            => __( 'Elenco progetti', 'cyberalchimista' ),
        'items_list_navigation' => __( 'Navigazione elenco progetti', 'cyberalchimista' ),
        'filter_items_list'     => __( 'Filtra elenco progetti', 'cyberalchimista' ),
    );
    $args = array(
        'label'                 => __( 'Progetto', 'cyberalchimista' ),
        'description'           => __( 'Contenuti relativi ai progetti realizzati', 'cyberalchimista' ),
        'labels'                => $labels,
        'supports'              => array( 'title', 'editor', 'thumbnail', 'excerpt', 'custom-fields', 'revisions' ), // Cosa supporta il CPT
        'taxonomies'            => array(), // Aggiungeremo le tassonomie personalizzate qui dopo
        'hierarchical'          => false, // I progetti non sono gerarchici (come le pagine)
        'public'                => true,  // Visibile nel frontend e backend
        'show_ui'               => true,  // Mostra l'interfaccia di amministrazione
        'show_in_menu'          => true,  // Mostra nel menu di amministrazione
        'menu_position'         => 5,     // Posizione nel menu (5 sotto Articoli)
        'menu_icon'             => 'dashicons-portfolio', // Icona Dashicon
        'show_in_admin_bar'     => true,  // Mostra nella barra di amministrazione
        'show_in_nav_menus'     => true,  // Selezionabile nei menu di navigazione
        'can_export'            => true,  // Può essere esportato
        'has_archive'           => 'progetti', // Abilita l'archivio all'URL /progetti/
        'exclude_from_search'   => false, // Incluso nei risultati di ricerca del sito
        'publicly_queryable'    => true,  // Queryabile pubblicamente (necessario per visualizzazione frontend)
        'capability_type'       => 'post', // Gestisce i permessi come i post standard (o 'page', o array custom)
        'show_in_rest'          => true, // Abilita l'API REST per questo CPT
        'rewrite'               => array( 'slug' => 'progetto', 'with_front' => false ), // Struttura URL: /progetto/nome-progetto/
    );
    register_post_type( 'progetto', $args ); // 'progetto' è lo slug univoco del CPT

}
add_action( 'init', 'cyba_register_project_post_type', 0 );

// Importante: Dopo aver aggiunto/modificato il codice CPT/tassonomie,
// vai su Impostazioni > Permalink e clicca "Salva le modifiche"
// per rigenerare le rewrite rules di WordPress.

Analizziamo alcuni argomenti chiave di $args:

  • labels: Un array che definisce tutte le etichette testuali usate nell’interfaccia di amministrazione per questo CPT. È fondamentale per l’usabilità e l’internazionalizzazione (vedi l’uso di _x(), __() e il text domain cyberalchimista).
  • supports: Specifica quali metabox e funzionalità standard di WordPress abilitare per questo CPT (titolo, editor WYSIWYG, immagine in evidenza, riassunto, campi personalizzati, revisioni, ecc.).
  • public: Un meta-argomento che imposta automaticamente publicly_queryable, show_ui, show_in_nav_menus e exclude_from_search a true. Utile per CPT destinati al frontend.
  • hierarchical: Se true, il CPT si comporterà come le Pagine (con relazioni padre-figlio). Se false (default), si comporterà come gli Articoli.
  • has_archive: Se impostato a true o a una stringa (che diventa lo slug dell’archivio, es. progetti), WordPress creerà automaticamente una pagina di archivio per questo CPT (accessibile a tuosito.it/progetti/).
  • rewrite: Controlla la struttura dei permalink per i singoli CPT. Con ['slug' => 'progetto', 'with_front' => false], l’URL di un singolo progetto sarà tuosito.it/progetto/nome-del-progetto/. with_front => false impedisce che il prefisso della struttura permalink globale (es. /blog/) venga anteposto.
  • menu_icon: Permette di specificare un’icona dal set Dashicons di WordPress per il menu laterale.
  • show_in_rest: Fondamentale se prevedi di usare l’editor a blocchi (Gutenberg) o interagire con questo CPT tramite l’API REST di WordPress.
  • capability_type: Definisce come WordPress gestirà i permessi. Usando post, i permessi saranno edit_post, publish_posts, delete_post, ecc., ma riferiti a progetto. Puoi anche definire capacità completamente custom per un controllo granulare degli accessi.

Come creare Tassonomie Personalizzate

Similmente ai CPT, le tassonomie si registrano con la funzione register_taxonomy(), sempre agganciata all’hook init. È importante registrarle dopo la registrazione del CPT a cui si vogliono associare, anche se l’ordine dentro la stessa funzione init non è strettamente vincolante (WordPress le gestisce correttamente).

Differenza tra Tassonomie Gerarchiche e Non Gerarchiche:

  • Gerarchiche (hierarchical = true): Funzionano come le Categorie. Possono avere relazioni padre-figlio (es. Web > Sviluppo Backend > PHP). L’interfaccia di amministrazione usa checkbox.
  • Non Gerarchiche (hierarchical = false): Funzionano come i Tag. Sono “piatte”, senza gerarchia. L’interfaccia usa un campo di input con autocompletamento.

Esempio Pratico: Creiamo le tassonomie “Tipologia” (gerarchica) e “Tecnologia” (non gerarchica) per i “Progetti”

<?php
/**
 * Registra le tassonomie personalizzate per il CPT "Progetti".
 * Agganciato all'hook 'init'.
 */
function cyba_register_project_taxonomies() {

    // Tassonomia Gerarchica: Tipologia Progetto
    $labels_tipologia = array(
        'name'              => _x( 'Tipologie Progetto', 'taxonomy general name', 'cyberalchimista' ),
        'singular_name'     => _x( 'Tipologia Progetto', 'taxonomy singular name', 'cyberalchimista' ),
        'search_items'      => __( 'Cerca Tipologie', 'cyberalchimista' ),
        'all_items'         => __( 'Tutte le Tipologie', 'cyberalchimista' ),
        'parent_item'       => __( 'Tipologia Genitore', 'cyberalchimista' ),
        'parent_item_colon' => __( 'Tipologia Genitore:', 'cyberalchimista' ),
        'edit_item'         => __( 'Modifica Tipologia', 'cyberalchimista' ),
        'update_item'       => __( 'Aggiorna Tipologia', 'cyberalchimista' ),
        'add_new_item'      => __( 'Aggiungi Nuova Tipologia', 'cyberalchimista' ),
        'new_item_name'     => __( 'Nuovo Nome Tipologia', 'cyberalchimista' ),
        'menu_name'         => __( 'Tipologie', 'cyberalchimista' ),
    );
    $args_tipologia = array(
        'hierarchical'      => true, // GERARCHICA (come le categorie)
        'labels'            => $labels_tipologia,
        'show_ui'           => true,
        'show_admin_column' => true, // Mostra colonna nella tabella dei Progetti
        'query_var'         => true,
        'rewrite'           => array( 'slug' => 'tipologia-progetto' ), // URL: /tipologia-progetto/nome-tipologia/
        'show_in_rest'      => true, // Abilita per API REST e Gutenberg
    );
    // Il secondo argomento è l'array dei CPT a cui associarla!
    register_taxonomy( 'tipologia_progetto', array( 'progetto' ), $args_tipologia );


    // Tassonomia Non Gerarchica: Tecnologia Usata
    $labels_tecnologia = array(
        'name'                       => _x( 'Tecnologie', 'taxonomy general name', 'cyberalchimista' ),
        'singular_name'              => _x( 'Tecnologia', 'taxonomy singular name', 'cyberalchimista' ),
        'search_items'               => __( 'Cerca Tecnologie', 'cyberalchimista' ),
        'popular_items'              => __( 'Tecnologie Popolari', 'cyberalchimista' ),
        'all_items'                  => __( 'Tutte le Tecnologie', 'cyberalchimista' ),
        'parent_item'                => null, // Non necessario per non-gerarchiche
        'parent_item_colon'          => null, // Non necessario per non-gerarchiche
        'edit_item'                  => __( 'Modifica Tecnologia', 'cyberalchimista' ),
        'update_item'                => __( 'Aggiorna Tecnologia', 'cyberalchimista' ),
        'add_new_item'               => __( 'Aggiungi Nuova Tecnologia', 'cyberalchimista' ),
        'new_item_name'              => __( 'Nuovo Nome Tecnologia', 'cyberalchimista' ),
        'separate_items_with_commas' => __( 'Separa tecnologie con virgole', 'cyberalchimista' ),
        'add_or_remove_items'        => __( 'Aggiungi o rimuovi tecnologie', 'cyberalchimista' ),
        'choose_from_most_used'      => __( 'Scegli dalle tecnologie più usate', 'cyberalchimista' ),
        'not_found'                  => __( 'Nessuna tecnologia trovata', 'cyberalchimista' ),
        'menu_name'                  => __( 'Tecnologie', 'cyberalchimista' ),
    );
    $args_tecnologia = array(
        'hierarchical'          => false, // NON GERARCHICA (come i tag)
        'labels'                => $labels_tecnologia,
        'show_ui'               => true,
        'show_admin_column'     => true,
        'query_var'             => true,
        'rewrite'               => array( 'slug' => 'tecnologia-usata' ), // URL: /tecnologia-usata/nome-tecnologia/
        'show_in_rest'          => true,
    );
    // Associa anche questa al CPT 'progetto'
    register_taxonomy( 'tecnologia_usata', array( 'progetto' ), $args_tecnologia );
}
add_action( 'init', 'cyba_register_project_taxonomies', 0 );

// Ricorda: Salva i Permalink dopo aver aggiunto/modificato il codice!

Elementi chiave in register_taxonomy():

  • Primo argomento: Lo slug univoco della tassonomia (es. tipologia_progetto).
  • Secondo argomento: Un array contenente gli slug dei post type a cui questa tassonomia deve essere associata (es. array('progetto')). Puoi associare una tassonomia a più post type, inclusi quelli standard come post (es. array('progetto', 'post')).
  • Terzo argomento: L’array $args con le impostazioni, simile a register_post_type().
    • hierarchical: Definisce se è gerarchica o meno.
    • show_admin_column: Molto utile! Se true, aggiunge una colonna nella schermata di elenco del CPT associato, mostrando i termini di questa tassonomia assegnati a ciascun elemento.
    • rewrite: Controlla lo slug dell’URL per gli archivi di questa tassonomia (es. tuosito.it/tipologia-progetto/web/).

Visualizzazione dei CPT e delle Tassonomie nel Tema

Ora che abbiamo definito la struttura nel backend, dobbiamo visualizzare questi contenuti nel frontend. WordPress utilizza una gerarchia di template per decidere quale file del tema usare per mostrare diversi tipi di contenuto.

Per i nostri CPT “Progetti” (con slug progetto), WordPress cercherà i seguenti file, in questo ordine:

  • Visualizzazione Singolo Progetto:
    1. single-progetto.php
    2. single.php
    3. singular.php
    4. index.php
  • Visualizzazione Archivio Progetti (es. tuosito.it/progetti/):
    1. archive-progetto.php
    2. archive.php
    3. index.php
  • Visualizzazione Archivio Tassonomia “Tipologia Progetto”:
    1. taxonomy-tipologia_progetto.php
    2. taxonomy.php
    3. archive.php
    4. index.php
  • Visualizzazione Archivio Tassonomia “Tecnologia Usata”:
    1. taxonomy-tecnologia_usata.php
    2. taxonomy.php
    3. archive.php
    4. index.php

Quindi, per personalizzare la visualizzazione, dovrai creare i file template appropriati (es. single-progetto.php e archive-progetto.php) nella cartella del tuo tema. All’interno di questi file, userai il classico Loop di WordPress per ciclare e mostrare i dati dei progetti.

Query Personalizzate con WP_Query

A volte potresti aver bisogno di recuperare e visualizzare i tuoi CPT in punti specifici del sito (es. una sezione “Ultimi Progetti” in homepage) o con filtri complessi. Qui entra in gioco la classe WP_Query.

Esempio: Mostrare gli ultimi 3 progetti della tipologia “Web”:

<?php
$args_query = array(
    'post_type'      => 'progetto',       // Specifica il CPT
    'post_status'    => 'publish',        // Solo progetti pubblicati
    'posts_per_page' => 3,                // Numero di risultati
    'orderby'        => 'date',           // Ordina per data
    'order'          => 'DESC',           // Dal più recente
    'tax_query'      => array(            // Filtra per tassonomia
        array(
            'taxonomy' => 'tipologia_progetto', // Nome della tassonomia
            'field'    => 'slug',             // Campo usato per identificare il termine (slug, term_id, name)
            'terms'    => 'web',              // Lo slug del termine desiderato
        ),
    ),
);

$progetti_web = new WP_Query( $args_query );

if ( $progetti_web->have_posts() ) :
    echo '<ul>';
    while ( $progetti_web->have_posts() ) : $progetti_web->the_post();
        // Qui usi le template tags standard di WordPress
        echo '<li><a href="' . esc_url( get_permalink() ) . '">' . esc_html( get_the_title() ) . '</a></li>';
    endwhile;
    echo '</ul>';
    wp_reset_postdata(); // Fondamentale dopo un loop custom con WP_Query
else :
    echo '<p>Nessun progetto web trovato.</p>';
endif;
?>

La tax_query è particolarmente potente e permette condizioni multiple (relazione AND o OR) per filtri complessi basati su più tassonomie. Ricorda sempre di usare wp_reset_postdata() dopo un loop personalizzato per ripristinare la query globale di WordPress.

Best Practice e Sicurezza

  1. Posizionamento del Codice: Come accennato, evita functions.php per codice strutturale. Usa un plugin specifico per il sito o un mu-plugin. Questo rende il codice indipendente dal tema e più manutenibile.
  2. Text Domain: Usa sempre un text domain univoco (es. cyberalchimista negli esempi) e le funzioni di internazionalizzazione (__, _x, esc_html__, etc.) per tutte le stringhe visibili all’utente. Questo permette la traduzione del tuo codice. Vedi la guida all’internazionalizzazione di WordPress.
  3. Sicurezza nell’Output: Quando visualizzi dati provenienti dai CPT (titoli, contenuto, metadati) nei template, assicurati sempre di effettuare l’escape corretto usando funzioni come esc_html(), esc_url(), esc_attr(), wp_kses_post() a seconda del contesto, per prevenire attacchi XSS. Approfondisci in La Sicurezza Frontend nel 2025: Guida Pratica su XSS e CORS.
  4. Controllo delle Capability: Per progetti che richiedono ruoli utente e permessi specifici, considera l’uso dell’argomento capability_type in register_post_type() insieme all’argomento capabilities per definire permessi custom (es. edit_progetto, publish_progetti, delete_progetti) e assegnarli ai ruoli appropriati.
  5. Prefissi: Usa prefissi univoci per i nomi delle tue funzioni (es. cyba_) per evitare conflitti con altre funzioni di WordPress, del tema o di altri plugin.
  6. Codice Pulito: Mantieni il codice organizzato, commentato e segui gli Standard di Codifica di WordPress per la leggibilità e la manutenibilità.
  7. Flush Rewrite Rules: Ricorda sempre di andare su Impostazioni > Permalink e cliccare “Salva le modifiche” dopo aver aggiunto o modificato CPT o tassonomie nel codice. Questo forza WordPress a rigenerare le sue regole di riscrittura degli URL, rendendo i nuovi permalink funzionanti. Fallo solo durante lo sviluppo, non programmaticamente su ogni caricamento di pagina!

Conclusione

Creare WordPress Custom Post Type e tassonomie personalizzate via codice è una skill fondamentale per ogni sviluppatore WordPress che miri all’eccellenza. Sebbene richieda uno sforzo iniziale maggiore rispetto all’uso di plugin, i vantaggi in termini di performance, controllo, flessibilità e comprensione della piattaforma sono enormi.

Checklist Rapida:

  • [ ] Scegli uno slug univoco per il CPT (es. progetto).
  • [ ] Definisci le labels per l’interfaccia (usa text domain!).
  • [ ] Imposta gli supports necessari (title, editor, thumbnail…).
  • [ ] Decidi se è hierarchical o meno.
  • [ ] Abilita public, show_ui, show_in_menu.
  • [ ] Configura has_archive e rewrite per gli URL.
  • [ ] Considera menu_icon, show_in_rest, capability_type.
  • [ ] Registra il CPT con register_post_type sull’hook init.
  • [ ] Crea tassonomie con register_taxonomy (gerarchiche/non), definendo labels e rewrite.
  • [ ] Associa le tassonomie al CPT corretto.
  • [ ] Posiziona il codice in un plugin custom o mu-plugin.
  • [ ] Vai su Impostazioni > Permalink e salva per aggiornare le rewrite rules.
  • [ ] Crea i file template necessari nel tema (single-{cpt}.php, archive-{cpt}.php).
  • [ ] Usa WP_Query per loop personalizzati dove serve (ricorda wp_reset_postdata()).
  • [ ] Effettua sempre l’escape dell’output (esc_html, esc_url…).

Padroneggiare CPT e tassonomie via codice non solo migliora la struttura e l’organizzazione dei tuoi contenuti, ma apre anche la porta a ottimizzazioni SEO più mirate (SEO tecnica per developer: guida completa), una User Experience (UX) più fluida e, soprattutto, performance web superiori, fattori sempre più cruciali nel panorama digitale attuale

Condividi

Articoli Recenti

Categorie popolari