
Si desarrollas temas para WordPress, probablemente tienes decenas de pestañas abiertas buscando el nombre exacto de un hook, la jerarquía de plantillas o la sintaxis correcta de theme.json. Esta guía de referencia rápida te da todo eso en un solo lugar, actualizada: tanto si trabajas con temas clásicos como si ya te has pasado a los Block Themes y el Full Site Editing (FSE).
A diferencia de la cheat sheet original de 2012 que publicamos aquí, esta versión cubre desde los hooks fundamentales de siempre hasta la estructura de theme.json y los comandos WP-CLI que más usarás en el día a día. Guárdala en favoritos o imprímela: vas a volver a ella.
Un tema clásico se construye con una jerarquía de archivos PHP que WordPress carga según el contexto de la página. Aquí tienes los archivos esenciales:
| Archivo | Cuándo se usa |
|---|---|
index.php | Plantilla de reserva universal (obligatoria) |
style.css | Hoja de estilos principal + cabecera de metadatos del tema (obligatoria) |
functions.php | Registro de funciones, hooks, enqueue de scripts/styles |
header.php | Cabecera del sitio, cargada con get_header() |
footer.php | Pie de página, cargado con get_footer() |
sidebar.php | Barra lateral, cargada con get_sidebar() |
single.php | Entrada individual de blog |
page.php | Página estática |
archive.php | Páginas de archivo (categorías, etiquetas, fechas) |
category.php | Archivo de categoría (sobrescribe archive.php) |
tag.php | Archivo de etiqueta |
search.php | Resultados de búsqueda |
404.php | Página de error 404 |
home.php | Listado de entradas del blog (cuando la portada es estática) |
front-page.php | Portada del sitio (estática o dinámica) |
singular.php | Plantilla compartida para single + page (WP 4.3+) |
comments.php | Área de comentarios |
attachment.php | Páginas de adjuntos |
screenshot.png | Captura de pantalla (880×660 px) para el directorio de temas |
La jerarquía de plantillas funciona de más específico a más genérico. WordPress busca el archivo más específico disponible y, si no lo encuentra, sube en la cadena hasta llegar a index.php.
Los Block Themes, introducidos de forma estable en WordPress 5.9, cambian la estructura completamente. Ya no hay archivos PHP de plantilla: todo se define con bloques HTML en archivos .html, y la configuración global del tema vive en theme.json.
| Archivo / Carpeta | Función |
|---|---|
style.css | Cabecera de metadatos del tema (obligatoria, puede estar vacía) |
theme.json | Configuración global: colores, tipografía, espaciado, bloques |
templates/ | Plantillas de página en formato HTML con bloques (index.html obligatorio) |
parts/ | Partes reutilizables: header.html, footer.html, sidebar.html |
patterns/ | Patrones de bloques registrados automáticamente |
functions.php | Opcional: enqueue de scripts adicionales, hooks PHP |
assets/ | Imágenes, fonts, JS/CSS personalizados |
mi-tema/
├── style.css # Cabecera con Theme Name, Version, etc.
├── theme.json # Configuración global
├── templates/
│ ├── index.html # Obligatoria
│ ├── single.html
│ ├── page.html
│ ├── archive.html
│ ├── search.html
│ └── 404.html
└── parts/
├── header.html
└── footer.htmlLenguaje del código: PHP (php)
theme.json es el corazón de la configuración de un Block Theme (aunque también funciona en temas clásicos desde WP 5.8). Controla la paleta de colores, tipografía, espaciado y qué opciones del editor están disponibles para el usuario.
{
"$schema": "https://schemas.wp.org/trunk/theme.json",
"version": 3,
"settings": {
"color": {
"palette": [
{ "slug": "primary", "color": "#1a73e8", "name": "Primary" },
{ "slug": "secondary", "color": "#f8f9fa", "name": "Secondary" },
{ "slug": "dark", "color": "#202124", "name": "Dark" }
],
"custom": false,
"customDuotone": false
},
"typography": {
"fontFamilies": [
{
"slug": "system",
"name": "System UI",
"fontFamily": "-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif"
}
],
"fontSizes": [
{ "slug": "sm", "size": "0.875rem", "name": "Small" },
{ "slug": "base", "size": "1rem", "name": "Base" },
{ "slug": "lg", "size": "1.25rem", "name": "Large" },
{ "slug": "xl", "size": "1.5rem", "name": "Extra Large" }
],
"customFontSize": false
},
"spacing": {
"spacingSizes": [
{ "slug": "sm", "size": "1rem", "name": "Small" },
{ "slug": "md", "size": "2rem", "name": "Medium" },
{ "slug": "lg", "size": "4rem", "name": "Large" }
],
"customSpacingSize": false
},
"layout": {
"contentSize": "800px",
"wideSize": "1200px"
}
},
"styles": {
"color": {
"background": "var(--wp--preset--color--secondary)",
"text": "var(--wp--preset--color--dark)"
},
"typography": {
"fontFamily": "var(--wp--preset--font-family--system)",
"fontSize": "var(--wp--preset--font-size--base)",
"lineHeight": "1.7"
},
"elements": {
"link": {
"color": { "text": "var(--wp--preset--color--primary)" }
}
}
}
}Lenguaje del código: JSON / JSON con comentarios (json)
Los hooks son el mecanismo central para personalizar WordPress sin tocar el núcleo. Hay dos tipos: acciones (add_action) que ejecutan código en un momento concreto, y filtros (add_filter) que modifican datos antes de que se usen.
| Hook | Cuándo se dispara | Uso típico |
|---|---|---|
after_setup_theme | Tras cargar el tema | add_theme_support(), register_nav_menus() |
init | Después de cargar WP, antes de headers | Registrar CPTs, taxonomías, shortcodes |
wp_enqueue_scripts | Front-end, para encolar assets | wp_enqueue_style(), wp_enqueue_script() |
admin_enqueue_scripts | Back-end, para encolar assets del admin | Scripts/styles solo en wp-admin |
wp_head | Dentro de <head> | Meta tags, estilos inline críticos |
wp_body_open | Justo tras <body> | GTM, banners de cookies |
wp_footer | Antes de cerrar </body> | Scripts diferidos, chat widgets |
save_post | Al guardar/actualizar una entrada | Metadatos personalizados, invalidar caché |
widgets_init | Al inicializar widgets | register_sidebar() |
template_redirect | Antes de determinar la plantilla | Redirecciones condicionales |
wp_loaded | WordPress completamente cargado | Lógica que necesita todo WP listo |
the_post | Dentro del loop, al configurar la entrada | Modificar datos del post actual |
| Filtro | Qué modifica |
|---|---|
the_content | Contenido del post antes de mostrarse |
the_title | Título del post en el front-end |
excerpt_length | Longitud del extracto automático |
excerpt_more | Texto «[…]» al final del extracto |
wp_title | Título de la página (legacy, usar wp_head) |
body_class | Clases CSS del elemento <body> |
post_class | Clases CSS de cada entrada en el loop |
upload_mimes | Tipos MIME permitidos en subidas |
login_redirect | URL de redirección tras el login |
allowed_block_types_all | Bloques de Gutenberg disponibles (FSE) |
block_editor_settings_all | Configuración global del editor de bloques |
// Acción: ejecutar código en un punto concreto
add_action( 'after_setup_theme', function() {
add_theme_support( 'post-thumbnails' );
add_theme_support( 'title-tag' );
add_theme_support( 'html5', [ 'search-form', 'comment-form', 'gallery', 'caption' ] );
register_nav_menus( [ 'primary' => __( 'Menú principal', 'mi-tema' ) ] );
} );
// Filtro: modificar datos antes de su uso
add_filter( 'excerpt_length', function( $length ) {
return 25; // palabras
} );
// Prioridad y argumentos (por defecto: prioridad 10, 1 argumento)
add_action( 'save_post', 'mi_funcion_guardar', 20, 2 );
add_filter( 'the_content', 'mi_filtro_contenido', 15, 1 );Lenguaje del código: PHP (php)
Las template tags son funciones PHP de WordPress para mostrar datos dentro de las plantillas. Estas son las que usarás constantemente:
| Función | Qué devuelve / hace |
|---|---|
the_title() | Imprime el título del post actual |
get_the_title( $id ) | Devuelve el título (sin imprimir) |
the_content() | Imprime el contenido del post con filtros aplicados |
the_excerpt() | Imprime el extracto (automático o manual) |
the_permalink() | Imprime la URL del post actual |
get_permalink( $id ) | Devuelve la URL del post (sin imprimir) |
the_ID() | Imprime el ID del post actual |
get_the_ID() | Devuelve el ID del post (sin imprimir) |
the_post_thumbnail( $size ) | Imprime la imagen destacada |
get_template_part( $slug, $name ) | Incluye una parte de plantilla reutilizable |
get_header() | Incluye header.php |
get_footer() | Incluye footer.php |
get_sidebar() | Incluye sidebar.php |
get_post_meta( $id, $key, true ) | Devuelve un metadato personalizado |
get_field( $name ) | Devuelve un campo de ACF (requiere plugin) |
bloginfo( 'name' ) | Nombre del sitio |
get_bloginfo( 'url' ) | URL del sitio (devuelve, no imprime) |
home_url( '/' ) | URL de la portada del blog |
get_template_directory_uri() | URL del directorio del tema activo |
get_stylesheet_directory_uri() | URL del tema hijo (o padre si no hay hijo) |
has_post_thumbnail() | Comprueba si el post tiene imagen destacada |
Uno de los errores más comunes en temas antiguos es cargar scripts con <script> directo en las plantillas. La forma correcta desde hace años es usar el sistema de enqueue de WordPress, que gestiona dependencias y evita duplicados.
add_action( 'wp_enqueue_scripts', 'mi_tema_enqueue_assets' );
function mi_tema_enqueue_assets() {
// Estilo principal (versión = versión del tema)
wp_enqueue_style(
'mi-tema-main',
get_stylesheet_uri(),
[],
wp_get_theme()->get( 'Version' )
);
// Script con dependencias y defer
wp_enqueue_script(
'mi-tema-main-js',
get_template_directory_uri() . '/assets/js/main.js',
[ 'jquery' ], // dependencias
'1.0.0',
[ 'strategy' => 'defer', 'in_footer' => true ] // WP 6.3+
);
// Pasar variables PHP a JS
wp_localize_script( 'mi-tema-main-js', 'miTema', [
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'mi-tema-nonce' ),
] );
}
// Solo en páginas de administración
add_action( 'admin_enqueue_scripts', function( $hook ) {
if ( 'post.php' !== $hook ) return;
wp_enqueue_style( 'mi-admin-style', get_template_directory_uri() . '/assets/css/admin.css' );
} );Lenguaje del código: PHP (php)
La seguridad no es opcional en el desarrollo de temas. Estas son las funciones que debes usar siempre antes de imprimir cualquier dato externo o recibido del usuario:
| Función | Cuándo usarla |
|---|---|
esc_html( $str ) | Texto genérico dentro de HTML (evita XSS) |
esc_attr( $str ) | Valores de atributos HTML (class, id, data-*) |
esc_url( $url ) | URLs en href, src, etc. |
esc_textarea( $str ) | Contenido dentro de <textarea> |
esc_js( $str ) | Cadenas dentro de bloques JavaScript inline |
wp_kses_post( $html ) | HTML del que quieres permitir solo etiquetas seguras |
absint( $num ) | Enteros positivos (IDs, contadores) |
intval( $num ) | Enteros (positivos y negativos) |
// Crear nonce en formulario
wp_nonce_field( 'guardar_metadatos', 'mi_nonce_field' );
// Verificar nonce al procesar
if ( ! isset( $_POST['mi_nonce_field'] ) ||
! wp_verify_nonce( $_POST['mi_nonce_field'], 'guardar_metadatos' ) ) {
wp_die( 'Verificación de seguridad fallida.' );
}
// Nonce en URL (para enlaces de acción)
$url = wp_nonce_url( admin_url( 'admin.php?action=mi-accion' ), 'mi-accion-nonce' );Lenguaje del código: PHP (php)
// Texto traducible (devuelve)
$texto = __( 'Leer más', 'mi-tema' );
// Texto traducible (imprime directamente)
_e( 'Publicado el', 'mi-tema' );
// Con contexto para el traductor
$texto = _x( 'Ver', 'botón de acción', 'mi-tema' );
// Plurales
$texto = _n( '%d comentario', '%d comentarios', $count, 'mi-tema' );
// Escapado + traducción en una línea (más seguro)
esc_html_e( 'Buscar', 'mi-tema' );
$safe = esc_html__( 'Resultados', 'mi-tema' );
// Registrar el dominio de texto en functions.php
add_action( 'after_setup_theme', function() {
load_theme_textdomain( 'mi-tema', get_template_directory() . '/languages' );
} );Lenguaje del código: PHP (php)
WordPress tiene un equipo dedicado a accesibilidad y los temas del directorio oficial deben cumplir unas normas mínimas. Aunque no publiques en el directorio, seguir estas convenciones mejora la experiencia de todos los usuarios y ayuda al SEO:
add_theme_support( 'html5', ['search-form', 'comment-form', 'comment-list', 'gallery', 'caption', 'style', 'script'] ) para markup HTML5 semántico.skip navigation link como primer elemento tras <body> (con wp_body_open).<img> deben tener atributo alt; usa esc_attr( get_post_meta( $id, '_wp_attachment_image_alt', true ) ).aria-label descriptivo: <nav aria-label="<?php esc_attr_e( 'Menú principal', 'mi-tema' ); ?>">.WP-CLI es la herramienta de línea de comandos oficial de WordPress. Si no la usas en tu flujo de desarrollo de temas, te estás complicando la vida innecesariamente. Estos son los comandos que más valor aportan:
# Crear un tema hijo a partir de un tema existente
wp scaffold child-theme mi-tema-hijo --parent_theme=twentytwentyfive
# Crear tema desde cero (estructura básica)
wp scaffold _s mi-tema --theme_name="Mi Tema" --author="Tu Nombre" --activate
# Listar temas instalados
wp theme list
# Activar un tema
wp theme activate mi-tema
# Ver detalles de un tema
wp theme get twentytwentyfive --fields=name,version,status
# Buscar temas en el repositorio oficial
wp theme search "minimalist" --per-page=5
# Actualizar todos los temas
wp theme update --all
# Exportar contenido de demostración
wp export --post_type=post --post_status=publish --filename_format=demo-content.xml
# Evaluar código PHP rápido
wp eval 'echo get_template_directory();'
# Vaciar caché de objetos
wp cache flush
# Regenerar miniaturas después de cambiar tamaños de imagen
wp media regenerate --yes
# Ver hooks disponibles (requiere plugin Query Monitor)
wp eval 'do_action( "wp_head" );'
# Exportar base de datos
wp db export backup.sql
# Buscar y reemplazar URL (migraciones)
wp search-replace 'https://old-domain.com' 'https://new-domain.com' --precise --recurse-objectsLenguaje del código: PHP (php)
Si quieres sacar más partido al rendimiento de tu tema, te recomendamos leer nuestra guía sobre cómo mejorar el rendimiento de WordPress. Y si trabajas con el .htaccess del servidor, esta otra sobre trucos útiles para el .htaccess de WordPress te será muy práctica.
Más allá de los hooks y funciones concretas, hay una serie de convenciones que separan un tema mantenible de uno que se convierte en deuda técnica a los seis meses:
mi_tema_ como prefijo para evitar conflictos con plugins. Nunca nombres genéricos como get_data().sanitize_text_field(), absint(), etc. al guardar datos del usuario; usa esc_html(), esc_url(), etc. al mostrarlos.functions.php o en archivos PHP organizados en una carpeta inc/.wp_enqueue_* para invalidar caché al actualizar. Usa filemtime( get_template_directory() . '/assets/css/main.css' ) durante desarrollo.theme.json para todo lo que puedas: colores, espaciado, tipografía. Evita CSS hardcoded; así el usuario puede cambiar el tema visual desde el editor sin tocar código.Depende del proyecto y del cliente. Los temas clásicos siguen siendo perfectamente válidos y tendrán soporte durante años. Sin embargo, si estás empezando un proyecto nuevo y el cliente usará el editor de WordPress activamente, los Block Themes ofrecen una experiencia de edición mucho más fluida. Lo ideal es dominar ambos paradigmas: la base PHP de los temas clásicos y la filosofía de theme.json y FSE de los Block Themes.
get_template_directory() y get_stylesheet_directory()?get_template_directory() siempre apunta al tema padre, mientras que get_stylesheet_directory() apunta al tema activo (que en un tema hijo sería el hijo, no el padre). Si estás en un tema sin hijo, ambas devuelven lo mismo. Para incluir archivos en un tema hijo, usa siempre get_stylesheet_directory(); para cargar assets del tema padre explícitamente, usa get_template_directory().
Usa add_theme_support() dentro del hook after_setup_theme. Las características más comunes son: post-thumbnails (imágenes destacadas), title-tag (delega el título al núcleo), html5 (markup moderno), custom-logo, automatic-feed-links y editor-styles. Para Block Themes modernos, muchas de estas se activan automáticamente, pero sigue siendo buena práctica declararlas explícitamente en functions.php.
Sí. Desde WordPress 5.8, theme.json está disponible también para temas clásicos, aunque con algunas limitaciones respecto a los Block Themes. Puedes usarlo para definir la paleta de colores del editor, tamaños de fuente personalizados y espaciado, lo que da al editor de bloques una experiencia más coherente con el diseño de tu tema clásico incluso sin FSE completo.
Esto no es específico del tema, pero afecta al rendimiento en desarrollo. La mayoría de plugins de caché excluyen el admin automáticamente. Si tu tema añade páginas de administración con assets pesados, recuerda usar admin_enqueue_scripts con verificación del hook concreto ($hook) en lugar de cargar en todas las páginas del admin. Puedes leer más sobre los mejores plugins de caché para WordPress en nuestra comparativa actualizada.
