Contents
Introducción
En WordPress, la clase WP_Query construye la consulta que obtiene los posts que se mostrarán en la página. pre_get_posts es un hook que permite interceptar y modificar esa consulta principal (y otras consultas) antes de que se ejecute. Usar pre_get_posts correctamente es la forma recomendada de adaptar el comportamiento del bucle principal sin romper la paginación ni las optimizaciones, a diferencia de query_posts(). Este artículo explica en detalle cómo usarlo, con ejemplos prácticos y buenas prácticas.
Conceptos clave
- is_main_query(): Devuelve true solo si la instancia de WP_Query es la consulta principal para esa petición. Es obligatorio usarlo cuando la intención es modificar únicamente la query principal.
- is_admin(): Indica si la petición es la zona administrativa. Normalmente se evita tocar las queries del admin a menos que sea intencionado.
- Condicionales de plantilla (is_home, is_archive, is_category, is_post_type_archive, is_search, is_single, is_author, is_date, etc.): sirven para delimitar cuándo aplicar una modificación.
- Prioridad del hook: pre_get_posts se engancha con add_action(pre_get_posts, mi_funcion, prioridad). Si hay otros hooks que alteran la consulta, ajustar la prioridad puede ser necesario.
- Evitar query_posts(): query_posts reescribe la query global y rompe paginación y cachés pre_get_posts es la alternativa correcta.
Buenas prácticas
- Siempre comprobar is_main_query() si la intención es afectar la consulta principal del frontend.
- Evitar tocar queries en admin a menos que sea intencional use is_admin() para filtrarlas o dejarlas pasar.
- No añadir consultas pesadas en meta_query o tax_query sin índices adecuados usar transients si el resultado es costoso.
- Para consultas secundarias (widgets, loops personalizados) crear una instancia de WP_Query en lugar de modificar la query global.
- Probar paginación y permalinks tras cualquier cambio (listas sin paginación suelen indicar que hubo un cambio incorrecto).
Ejemplo básico: cambiar posts_per_page en la página principal
Este ejemplo cambia el número de posts en la página de entradas (is_home()) a 6. Observa la comprobación de is_main_query() y is_admin() para no afectar queries secundarias ni el área administrativa.
is_main_query() ) { return } // Aplicar solo en la página principal de posts if ( is_home() ) { query->set( posts_per_page, 6 ) } } ?>
Modificar la query de un archivo de taxonomía o categoría
Si quieres alterar, por ejemplo, la ordenación y filtrar por un meta valor en un archivo de categoría, añade condicionales. Aquí se ordena por un meta campo rating de mayor a menor y se muestra 12 por página.
is_main_query() ) { return } if ( is_category() ) { query->set( posts_per_page, 12 ) query->set( meta_key, rating ) query->set( orderby, meta_value_num ) query->set( order, DESC ) // Opcional: solo posts con meta rating existente meta_query = array( array( key => rating, compare => EXISTS, ), ) query->set( meta_query, meta_query ) } } ?>
Excluir entradas (IDs / sticky posts) de la consulta principal
Para excluir contenidos puedes usar post__not_in o ignore_sticky_posts. Ejemplo que excluye ciertos IDs y que ignora los sticky posts para que no alteren el orden:
is_main_query() ) { return } if ( is_home() ) { // Excluir por ID excluir = array( 12, 34, 56 ) // IDs de ejemplo query->set( post__not_in, excluir ) // Ignorar sticky posts (no colocarlos arriba) query->set( ignore_sticky_posts, true ) } } ?>
Modificar archivo de tipo de post personalizado
En archives de un CPT puedes querer incluir posts privados o cambiar la tax_query. Aquí se muestra cómo mostrar solo posts publicados y ocultar revisiones, y cómo filtrar por una taxonomía personalizada producto_categoria.
is_main_query() ) { return } if ( is_post_type_archive( producto ) ) { query->set( posts_per_page, 20 ) query->set( post_status, publish ) // Filtrar por taxonomía si existe una query var if ( isset( _GET[categoria_producto] ) ) { query->set( tax_query, array( array( taxonomy => producto_categoria, field => slug, terms => sanitize_text_field( _GET[categoria_producto] ), ), ) ) } } } ?>
Offset con paginación: cuidado común y solución
Usar offset junto con paged puede romper la paginación estándar. Si necesitas un offset en la página principal y mantener paginación, hay que recalcular correctamente los parámetros. Ejemplo para aplicar un offset inicial en la primera página y mantener la paginación:
is_main_query() ! is_home() ) { return } // Offset deseado (por ejemplo, mostrar 2 posts destacados fuera de la paginación) offset = 2 posts_per_page = get_option( posts_per_page ) if ( ! posts_per_page ) { posts_per_page = 10 } // Si estamos en la primera página, aplicar offset normal paged = max( 1, get_query_var( paged ) ) if ( paged === 1 ) { query->set( posts_per_page, posts_per_page ) query->set( offset, offset ) } else { // Recalcular offset para páginas siguientes new_offset = offset ( ( paged - 1 ) posts_per_page ) query->set( posts_per_page, posts_per_page ) query->set( offset, new_offset ) } } ?>
Nota: este método funciona pero puede requerir ajustar enlaces de paginación generados y no es compatible con todas las funciones de WordPress. Alternativa recomendada: usar dos loops (uno para destacados, otro para el listado con paginación sin offset) en la plantilla.
Modificando consultas en admin (ejemplo seguro)
Si necesitas modificar la lista de posts en el admin (por ejemplo, limitar a un autor para un rol concreto), comprueba la pantalla actual con get_current_screen() y actúa solo cuando proceda.
is_main_query() ) { return } // Comprobar pantalla: solo en listado de posts screen = get_current_screen() if ( ! screen edit-post !== screen->id ) { return } // Ejemplo: si el usuario actual es editor limitar a sus posts if ( current_user_can( editor ) ! current_user_can( manage_options ) ) { user_id = get_current_user_id() query->set( author, user_id ) } } ?>
Consideraciones sobre REST API y Ajax
Las peticiones REST y AJAX también crean consultas. Si no quieres afectar estas peticiones, evita ejecutarlas en pre_get_posts usando comprobaciones:
- Para REST API: comprobar si está definida la constante REST_REQUEST (defined(REST_REQUEST) REST_REQUEST) o usar la comprobación is_rest() en versiones compatibles.
- Para AJAX: comprobar DOING_AJAX.
is_main_query() ) { return } // Aquí tus modificaciones seguras para el front-end principal... } ?>
Rendimiento y seguridad
- No crear meta_query o tax_query innecesariamente si son complejas, cachea los resultados con transients.
- Sanitiza cualquier entrada proveniente de _GET/_POST antes de usarla en set().
- Evita forzar JOINs o consultas que añadan demasiadas operaciones en cada carga considera preprocesado o índices en la base de datos para campos meta que uses en ordenación o búsquedas.
- Si tu cambio solo se necesita en algunas páginas concretas, usa condicionales lo más específicas posible para reducir el impacto.
Errores comunes y cómo resolverlos
- No comprobar is_main_query(): Esto afecta widgets y queries secundarios provocando comportamientos inesperados.
- Modificar la query en admin sin comprobar pantalla: puede romper listados administrativos.
- Usar offset sin manejar paged: rompe la paginación.
- Usar query_posts en lugar de pre_get_posts: rompe paginación y rendimiento.
Resumen rápido (tabla de comprobaciones útiles)
Comprueba | Cuándo usar |
is_main_query() | Si quieres modificar solo la query principal |
is_admin() | Evitar tocar queries del dashboard salvo que sea intencional |
is_home(), is_archive(), is_post_type_archive(), etc. | Condiciones para limitar la modificación en páginas concretas |
defined(REST_REQUEST) | Evitar afectar peticiones REST si no es necesario |
Últimos consejos
Utiliza pre_get_posts siempre que necesites ajustar la consulta principal. Mantén las comprobaciones claras y específicas, evita tocar el admin a menos que sepas lo que haces y documenta los cambios para futuros mantenedores. Cuando el ajuste es local a una plantilla, evalúa si no es mejor hacer dos loops en la plantilla en lugar de forzar la query global. Por último, prueba paginación, búsqueda y cachés después de cualquier cambio para asegurarte de que no hay efectos colaterales.
Referencia oficial: https://developer.wordpress.org/reference/hooks/pre_get_posts/
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |