Custom Post Types en WordPress: registro, taxonomías y buenas prácticas
Cuando WordPress no tiene un tipo de contenido nativo para lo que necesitas — productos, eventos, portfolios, recetas, propiedades — la solución son los Custom Post Types. Es uno de los mecanismos más potentes de WordPress y también uno de los más mal implementados.
En este artículo te enseño cómo registrar un CPT correctamente, cómo asociarle taxonomías propias y qué errores cometen casi todos los que empiezan con esto.
¿Qué es un Custom Post Type?
Internamente, WordPress almacena todos los contenidos en la tabla wp_posts. Posts, páginas, adjuntos, revisiones, menús de navegación — todo convive en la misma tabla diferenciado por el campo post_type.
Un CPT es simplemente un nuevo valor para ese campo. Cuando registras el tipo producto, WordPress empieza a reconocer entradas con post_type = 'producto' y les da su propio menú en el admin, sus propias URLs y sus propios templates.
Registrar un CPT con register_post_type()
El registro debe hacerse dentro del hook init. Nunca fuera de un hook:
add_action( 'init', 'mi_plugin_register_producto' );
function mi_plugin_register_producto() {
$labels = array(
'name' => 'Productos',
'singular_name' => 'Producto',
'add_new' => 'Añadir nuevo',
'add_new_item' => 'Añadir producto',
'edit_item' => 'Editar producto',
'view_item' => 'Ver producto',
'all_items' => 'Todos los productos',
'search_items' => 'Buscar productos',
'not_found' => 'No se encontraron productos.',
'not_found_in_trash' => 'No hay productos en la papelera.',
);
$args = array(
'labels' => $labels,
'public' => true,
'has_archive' => true,
'rewrite' => array( 'slug' => 'productos' ),
'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt' ),
'menu_icon' => 'dashicons-cart',
'show_in_rest' => true, // Obligatorio para Gutenberg
'menu_position' => 5,
);
register_post_type( 'producto', $args );
}
Los parámetros más importantes que debes entender:
- public: hace visible el CPT en el frontend y en el admin. Si es
false, solo existe internamente. - has_archive: crea una página de archivo en
/productos/que lista todos los productos. - rewrite: controla la estructura de URL. El slug aquí define la URL base.
- supports: qué meta boxes aparecen en el editor. Añade
'custom-fields'si quieres el panel de campos personalizados. - show_in_rest: imprescindible para que funcione el editor de bloques Gutenberg.
Registrar una Custom Taxonomy asociada
Un CPT sin taxonomías es como un blog sin categorías. Registra las taxonomías siempre en el mismo hook init, justo después del CPT:
add_action( 'init', 'mi_plugin_register_categorias_producto' );
function mi_plugin_register_categorias_producto() {
$labels = array(
'name' => 'Categorías de productos',
'singular_name' => 'Categoría',
'all_items' => 'Todas las categorías',
'edit_item' => 'Editar categoría',
'add_new_item' => 'Añadir categoría',
);
$args = array(
'labels' => $labels,
'hierarchical' => true, // true = como categorías, false = como tags
'public' => true,
'show_in_rest' => true,
'rewrite' => array( 'slug' => 'categoria-producto' ),
);
// Asociar la taxonomía al CPT 'producto'
register_taxonomy( 'categoria_producto', 'producto', $args );
}
La diferencia entre hierarchical: true y false: con true aparece como un árbol de checkboxes (igual que las categorías nativas). Con false, aparece como un campo de etiquetas.
El error más común: olvidar flush_rewrite_rules()
Después de registrar un CPT, las URLs no funcionan hasta que WordPress regenera sus reglas de reescritura. Si después de activar tu plugin las URLs del CPT dan error 404, este es el motivo.
La solución correcta es llamar a flush_rewrite_rules() en el hook de activación del plugin, no en cada carga de página:
register_activation_hook( __FILE__, 'mi_plugin_activar' );
function mi_plugin_activar() {
// Primero registra el CPT
mi_plugin_register_producto();
// Luego regenera las reglas
flush_rewrite_rules();
}
register_deactivation_hook( __FILE__, 'mi_plugin_desactivar' );
function mi_plugin_desactivar() {
flush_rewrite_rules();
}
Si no usas un plugin y registras el CPT directamente en el tema, ve a Ajustes → Enlaces permanentes y haz clic en «Guardar cambios» sin modificar nada. Eso regenera las reglas manualmente.
Templates para el CPT
WordPress busca templates específicos para los CPTs siguiendo la jerarquía de templates. Para el CPT producto:
- Entrada individual:
single-producto.php→single.php→index.php - Archivo/listado:
archive-producto.php→archive.php→index.php - Página de taxonomía:
taxonomy-categoria_producto.php→taxonomy.php→archive.php→index.php
WordPress siempre busca el archivo más específico primero y va subiendo hasta encontrar uno que exista.
¿CPT en el tema o en un plugin?
Esta es una pregunta frecuente y tiene una respuesta clara: siempre en un plugin.
Si registras el CPT en el tema y el cliente cambia de tema, pierde todo el contenido de ese tipo. Al estar en un plugin, el contenido persiste independientemente del tema activo.
La regla es sencilla: todo lo que sea datos va en un plugin. Todo lo que sea presentación va en el tema.
Conclusión
Los Custom Post Types son una de las herramientas más potentes de WordPress cuando se usan bien. La clave está en registrarlos correctamente en el hook init, asociarles taxonomías con sentido y hacer el flush de rewrite rules en la activación del plugin.
El siguiente paso después de dominar los CPTs es aprender a añadirles Meta Boxes para campos de datos personalizados — sin depender de ACF.
¿Qué sigue?
- → Meta Boxes: añadir campos personalizados sin ACF
- → WP_Query: cómo consultar CPTs con filtros y ordenación
- → Template Hierarchy explicada con ejemplos reales
