Hay un momento bastante típico cuando trabajas con el editor de bloques de WordPress: empiezas usando el bloque Query Loop para montar listados y todo va bien… hasta que deja de ser suficiente.
Quieres mostrar contenido relacionado, filtrar por un campo personalizado o adaptar el listado al contexto de la página. Y ahí es donde te das cuenta de que el bloque funciona, sí, pero solo hasta cierto punto.
El problema no es que el Query Loop esté mal planteado. De hecho, es una herramienta muy potente para construir listados dinámicos sin tocar código. El problema aparece cuando el proyecto necesita algo más de lógica: relaciones entre contenidos, condiciones específicas o automatización. En ese momento, las opciones del editor se quedan cortas.
Esto es especialmente útil en proyectos con WooCommerce, contenido editorial o cualquier sitio donde los listados dependan del contexto.
En este artículo vamos a ver cómo crear un Query loop block personalizado, manteniendo lo bueno del editor visual pero añadiendo la lógica necesaria desde código. Todo con un ejemplo real: mostrar productos de WooCommerce filtrados automáticamente según la página en la que estás.
La idea no es complicar el proyecto, sino justo lo contrario: tener control sin perder flexibilidad, y evitar soluciones a base de plugins que acaban pesando más de la cuenta.
Qué es el bloque Query Loop en WordPress (y qué sí hace bien)
El Query Loop en WordPress es, en esencia, una forma visual de trabajar con el clásico loop de siempre, pero sin tener que tocar plantillas PHP.
Permite mostrar listados dinámicos de contenido directamente desde el editor de bloques: entradas, páginas, productos de WooCommerce o cualquier tipo de contenido personalizado. Todo se configura desde la interfaz, eligiendo qué mostrar y cómo ordenarlo.
La ventaja clara es que puedes construir la estructura del listado con bloques. Por ejemplo, definir que cada elemento muestre la imagen destacada, el título, el precio o un botón. Esto hace que montar un grid de productos o un listado de posts sea rápido y bastante intuitivo, incluso sin perfil técnico.
Además, encaja muy bien con el enfoque de Gutenberg. Puedes reutilizar patrones, ajustar layouts y mantener cierta coherencia visual sin depender de plantillas rígidas. Para muchos proyectos, esto ya cubre una buena parte de las necesidades sin complicarse más.
El problema no está en lo que hace, sino en lo que no permite hacer cuando el proyecto crece. Y ahí es donde empieza lo interesante.
Dónde empieza el problema: límites del Query Loop estándar
Hasta aquí todo bien. El bloque cumple perfectamente cuando necesitas mostrar listados básicos: últimas entradas, productos recientes o contenido por categoría.
El problema aparece cuando el contenido deja de ser genérico y empieza a depender del contexto. Por ejemplo, cuando necesitas mostrar productos relacionados con la página actual, contenidos vinculados a un autor concreto o elementos filtrados por un campo personalizado.
Aquí es donde el bloque se queda corto. Desde el editor puedes configurar cosas como categorías, etiquetas o el número de resultados, pero no puedes definir lógica más avanzada. No hay forma directa de decirle algo como “muéstrame los productos cuyo campo personalizado coincide con este contenido”.
En proyectos reales esto pasa más de lo que parece. De hecho, es bastante habitual en tiendas WooCommerce, academias o webs con contenido relacionado. Y la solución rápida suele ser instalar un plugin para “loops avanzados” o grids dinámicos.
El problema de ese enfoque es que empiezas a acumular dependencias para resolver algo que, en el fondo, WordPress ya sabe hacer. Solo que no lo expone desde el editor.
Aquí es donde tiene sentido dar un paso más: mantener el bloque Query Loop, pero extender su comportamiento para que entienda el contexto y aplique filtros personalizados. Sin romper el editor y sin añadir capas innecesarias.
Caso real: productos recomendados por doctor en WooCommerce
Para que se entienda mejor, vamos a partir de un caso real desarrollado en Bubuku. Sin entrar en demasiado detalle, la idea es quedarnos con el enfoque y cómo se resuelve el problema. El proyecto era una farmacia online con WooCommerce, donde cada doctor tenía su propia ficha dentro de la web.
La necesidad era clara: cuando un usuario entraba en la página de un doctor, debía ver automáticamente los productos sobre los que ese profesional aporta recomendaciones de uso o contexto. No una selección manual hecha página por página, sino un listado dinámico que cambiase según el doctor consultado.
En el backend, cada producto estaba relacionado con uno o varios doctores mediante un campo personalizado. Así, WordPress tenía la información necesaria para saber qué productos estaban vinculados a cada ficha. El problema era otro: el listado estándar no podía usar esa relación desde el editor.
Con un Query Loop normal podíamos mostrar productos, ordenarlos o limitar la cantidad de resultados. Pero no podíamos decirle: “muestra solo los productos cuyo campo personalizado coincida con el doctor de esta página”.
Ahí es donde entra el Query loop block personalizado. La solución consistió en crear una variación del Loop pensada específicamente para este caso. El editor podía insertarla como cualquier otro bloque, pero por debajo WordPress aplicaba la lógica necesaria para filtrar los productos según el contexto de la página.
El resultado era mucho más coherente: el contenido no se centraba en empujar la venta, sino en reforzar la credibilidad del sitio, mostrando productos donde realmente se aporta valor desde el conocimiento de los doctores. Y a nivel de gestión, el equipo no tenía que duplicar listados ni configurar nada manualmente.
Cómo crear un Query Loop block personalizado (enfoque general)
Antes de meternos en código, conviene entender bien el enfoque. Porque aquí hay un error bastante habitual: pensar que necesitas crear un bloque nuevo desde cero. Y en la mayoría de casos, no hace falta.
La idea es mucho más simple y práctica: partimos del bloque Query Loop que ya existe y lo extendemos. Es decir, creamos una variación del bloque para usarla desde el editor y, por otro lado, modificamos su comportamiento para que aplique la lógica que necesitamos.
Esto implica trabajar en dos capas:
Por un lado, en el editor, registramos una variación del bloque con JavaScript. Aquí definimos cómo se comporta el bloque a nivel visual: qué tipo de contenido muestra, qué estructura tiene y cómo aparece en el inserter.
Por otro lado, en el backend, usamos PHP para modificar la query que ejecuta ese bloque. Es aquí donde añadimos la lógica real: filtrar por campo personalizado, usar el contexto de la página o cualquier condición que necesite el proyecto.
Lo importante es entender que estas dos partes están conectadas. La variación del bloque nos sirve para identificarlo y usarlo fácilmente desde el editor, mientras que PHP se encarga de decidir qué contenido se muestra realmente.
Este enfoque tiene varias ventajas claras:
- Mantienes la experiencia visual del editor de bloques
- Evitas crear bloques complejos innecesarios
- Puedes aplicar lógica avanzada sin depender de plugins
- Y todo queda bajo control dentro del propio proyecto
En el fondo, lo que estás haciendo es darle al Query Loop la capacidad de “entender” el contexto. Y eso es justo lo que le falta cuando trabajas solo desde el editor.
Cómo registrar una variación del bloque Query Loop con JavaScript
Para crear un Query loop block personalizado, vamos a apoyarnos en una funcionalidad muy útil de WordPress: permite extender el bloque Query creando variaciones sobre core/query.
En este caso, vamos a crear una variación pensada para mostrar productos de WooCommerce relacionados con el contexto de una ficha de doctor. A nivel de editor, el bloque aparecerá como una opción propia, pero internamente seguirá aprovechando toda la base del Query Loop nativo.
La variación se registra con JavaScript:
const PREFIX_PRODUCTS_BY_DOCTOR = 'prefix/products-by-doctor';
wp.blocks.registerBlockVariation('core/query', {
name: PREFIX_PRODUCTS_BY_DOCTOR,
title: 'Productos por Doctor',
description: 'Muestra los productos de un Doctor',
isActive: ({ namespace, query }) => {
return (
namespace === PREFIX_PRODUCTS_BY_DOCTOR &&
query.postType === 'product'
);
},
icon: 'admin-users',
scope: ['inserter'],
attributes: {
namespace: PREFIX_PRODUCTS_BY_DOCTOR,
query: {
postType: 'product',
},
align: 'full',
},
allowedControls: ['order'],
innerBlocks: [
[
'core/post-template',
{
layout: {
type: 'grid',
columnCount: 5,
},
},
[
['woocommerce/product-image'],
['core/post-title', { level: 3, isLink: true }],
['woocommerce/product-price'],
['woocommerce/product-button'],
],
],
['core/query-pagination'],
['core/query-no-results'],
],
});En este código estamos registrando una variación del bloque core/query pensada para mostrar productos relacionados con un doctor. Aunque internamente sigue siendo un Query Loop estándar, en el editor se comporta como un bloque independiente con un propósito claro.
La función isActive se utiliza para identificar cuándo esta variación está activa. Gracias a ella, WordPress puede reconocer que el Query Loop insertado pertenece a esta variación concreta y no a otro bloque de consulta. Esto es importante para mantener la coherencia cuando se edita o se reutiliza.
Dentro de la propiedad innerBlocks definimos la estructura visual del loop. Aquí se establece el layout en formato grid y los bloques que se mostrarán para cada producto, como la imagen, el título, el precio o el botón de compra. De esta forma, el diseño queda preconfigurado y el editor no tiene que construirlo manualmente cada vez.
Además, se asigna un namespace personalizado a la variación. Este identificador será clave en el siguiente paso, ya que nos permitirá detectar este Query Loop desde PHP y modificar su consulta para filtrar los productos según el contexto de la página.
Una vez cargues este script en el panel de administración, la variación estará disponible en el editor con el nombre «Productos por Doctor», lista para insertarse como cualquier otro bloque.

Configuración del bloque personalizado que permite mostrar productos seleccionados por un doctor desde el editor de WordPress.

Estructura interna del bloque “Productos por Doctor”, con todos los componentes necesarios para mostrar productos recomendados en una tienda online.
En la siguiente sección veremos cómo insertar el archivo JavaScript correctamente en el admin de WordPress y conectar toda esta lógica.
Cómo modificar la query del Query Loop con PHP (filtrado por contexto)
Hasta ahora tenemos el bloque listo en el editor. Podemos insertarlo, tiene su estructura y su diseño… pero sigue siendo un listado genérico. Todavía no sabe qué productos debe mostrar en cada página.
Aquí es donde entra PHP.
La idea es sencilla: cuando WordPress va a renderizar el bloque, interceptamos ese momento y modificamos la query para aplicar nuestro filtro personalizado. En este caso, filtrar los productos según el doctor de la página actual.
Para hacerlo, podemos usar el filtro pre_render_block, que nos permite actuar justo antes de que el bloque se genere en el frontend.
// Filtrar el bloque Query Loop por doctor
add_filter( 'pre_render_block', 'prefix_render_block_products_by_doctor', 10, 2 );
function prefix_render_block_products_by_doctor( $pre_render, $parsed_block ) {
// Verificamos que sea nuestra variación personalizada
if ( isset($parsed_block['attrs']['namespace']) && 'prefix/products-by-doctor' === $parsed_block['attrs']['namespace'] ) {
add_filter( 'query_loop_block_query_vars', function( $query, $block ) {
// Obtener el post actual (el doctor)
$post = get_post();
$query['meta_query'] = array(
array(
'key' => 'doctor_ID',
'value' => $post->ID,
'compare' => '='
)
);
return $query;
}, 10, 2 );
}
return $pre_render;
}Qué está pasando aquí (sin complicarlo)
Hay tres ideas clave que conviene entender:
La primera es que usamos el namespace que definimos en JavaScript para saber si estamos trabajando con nuestro bloque. Esto evita afectar a otros Query Loop que pueda haber en la página.
La segunda es que accedemos al contexto actual, en este caso la ficha del doctor (get_post()), para saber qué ID necesitamos usar como referencia.
La tercera es el uso de meta_query, que nos permite filtrar los productos según el valor de un campo personalizado. Aquí es donde realmente ocurre la magia: pasamos de un listado genérico a un contenido dinámico que cambia según la página.
Con esto, el bloque deja de ser estático y empieza a tener sentido dentro del proyecto.
Este filtro se ejecuta en cada render del bloque. Si haces cambios en el JavaScript o en la lógica, es posible que tengas que reinsertar el bloque en el editor para verlos reflejados correctamente.
Antes de cerrar, hay algunos puntos que conviene tener en cuenta para evitar errores habituales al trabajar con un Query loop block personalizado.
| Problema | Qué revisar |
|---|---|
| El bloque no aparece en el editor | Comprueba que el JavaScript está correctamente encolado, que el scope incluye inserter y que el namespace coincide. |
| Los cambios en el JS no se aplican | Elimina el bloque y vuelve a insertarlo. WordPress guarda su estructura y no actualiza automáticamente los cambios. |
| El filtro no funciona | Revisa que el namespace coincide en PHP y que el filtro query_loop_block_query_vars se aplica dentro de pre_render_block. |
| Resultados incorrectos | Verifica el meta_query y asegúrate de que el valor usado coincide con el ID o dato esperado. |
| Dudas sobre atributos del bloque | Usa console.log() en JavaScript o error_log() en PHP para inspeccionar los datos que recibe el bloque. |
Consejo: activa el modo debug del navegador y vacía la caché (del navegador o del plugin) durante el desarrollo.
Personalizar el diseño del loop con bloques propios
Una de las ventajas de este enfoque es que no estás limitado a usar solo los bloques básicos dentro del Query Loop personalizado. Puedes definir la estructura del listado con bloques nativos de WordPress, bloques de WooCommerce o incluso bloques propios creados para el proyecto.
En el ejemplo anterior usamos imagen, título, precio y botón de producto por separado. Es una base sencilla y funciona bien. Pero en un proyecto real puede interesarte tener una tarjeta más cuidada, con estilos propios, lógica adicional o campos específicos.
Por ejemplo, si ya tienes un bloque personalizado llamado prefix/product-card, puedes insertarlo directamente dentro del post-template:
innerBlocks: [
[
'core/post-template', {
layout: {
type: 'grid',
columnCount: 5
}
},
[
['prefix/product-card']
]
],
['core/query-pagination'],
['core/query-no-results']
]De esta forma, el diseño queda centralizado en un único bloque. Si más adelante necesitas cambiar la tarjeta del producto, no tienes que revisar cada loop del sitio: modificas el bloque y el cambio se aplica donde corresponda.
Esto es especialmente útil cuando trabajas con listados dinámicos en WordPress que se repiten en varias zonas del proyecto. Mantienes la lógica del Query Loop, pero evitas duplicar estructuras visuales y reduces el mantenimiento.
La idea es sencilla: el Query Loop decide qué contenido mostrar y tu bloque personalizado decide cómo se presenta ese contenido. Separar esas dos responsabilidades suele ahorrar muchos problemas a medio plazo.
Si quieres llevar este enfoque un paso más allá, puedes apoyarte en template parts para reutilizar estructuras completas dentro del loop.
Nota: si modificas el JavaScript después de insertar el bloque, recuerda eliminarlo del editor y volver a insertarlo para que coja los cambios.
Este enfoque no se limita solo a productos. Puedes aplicar la misma lógica a cualquier tipo de contenido, como por ejemplo artículos relacionados con un doctor.
En ese caso, puedes definir la estructura del loop utilizando bloques del core de WordPress, adaptando el contenido a un formato más editorial:
innerBlocks: [
[
'core/post-template', {
layout: {
type: 'grid',
columnCount: 5
}
},
[
['core/post-featured-image', { width: 148, height: 148 }],
['core/post-date', { format: 'd/m/Y' }],
['core/post-terms', { term: 'category', style: 'badge' }],
['core/post-title', { level: 2, isLink: true }],
['core/post-excerpt', { moreText: 'Leer más', wordCount: 20 }],
]
],
['core/query-pagination'],
['core/query-no-results']
]La lógica sigue siendo la misma. Lo único que cambia es el tipo de contenido y los bloques que utilizas dentro del loop. Esto te permite adaptar el Query Loop personalizado a distintos escenarios sin rehacer la base.
Cómo cargar el JavaScript en el editor
Para que esta variación funcione en WordPress, necesitas cargar el archivo JavaScript dentro del editor de bloques.
Esto se hace usando el hook enqueue_block_editor_assets, que permite añadir scripts solo en el editor y no en el frontend.
add_action( 'enqueue_block_editor_assets', 'prefix_enqueue_query_loop_variation' );
function prefix_enqueue_query_loop_variation() {
wp_enqueue_script(
'prefix-query-loop-variation',
plugin_dir_url( __FILE__ ) . 'query-loop-variation.js',
array( 'wp-blocks', 'wp-element', 'wp-editor' ),
'1.0',
true
);
}Con todo esto ya tienes un Query Loop completamente funcional, tanto a nivel visual como lógico. Antes de cerrar, merece la pena parar un momento y ver cuándo tiene sentido usar este enfoque y cuándo no.
Puedes incluir este código en un plugin personalizado o en el
functions.phpde tu tema, aunque para proyectos reales es más recomendable mantenerlo en un plugin.
Diseño personalizado WordPress: conclusiones finales
Llegados a este punto, el Query loop block personalizado es una solución muy potente. Pero no siempre es la opción adecuada, y aquí es donde conviene parar un momento y decidir con criterio.
Tiene sentido usar este enfoque cuando necesitas que el contenido responda al contexto. Por ejemplo, mostrar productos relacionados con una ficha, contenidos vinculados a un autor o listados que dependen de campos personalizados. En estos casos, el Query Loop estándar se queda corto y extenderlo te permite mantener el control sin perder la flexibilidad del editor.
También compensa cuando quieres evitar depender de plugins externos para algo que puedes resolver con código propio. En proyectos donde el rendimiento y el mantenimiento importan, reducir dependencias suele ser una buena decisión.
Ahora bien, no siempre merece la pena.
Si lo único que necesitas es mostrar entradas recientes, productos por categoría o listados simples, el bloque Query Loop nativo ya cumple perfectamente. Añadir lógica personalizada en estos casos solo complica el proyecto sin aportar valor real.
Tampoco es la mejor opción si el equipo que va a gestionar el contenido no tiene un mínimo contexto técnico. Aunque el bloque se use desde el editor, por debajo hay una lógica que conviene entender para evitar errores o comportamientos inesperados.
En resumen, este enfoque encaja cuando el contenido deja de ser genérico y pasa a ser dinámico y dependiente del contexto. Ahí es donde realmente marca la diferencia.
Y si estás en ese punto, merece la pena hacerlo bien desde el principio. En Bubuku trabajamos este tipo de soluciones en proyectos donde el contenido y la lógica van de la mano, buscando siempre un equilibrio real entre flexibilidad, rendimiento y facilidad de mantenimiento, a través de desarrollos a medida como plugins personalizados en WordPress.
Enlaces útiles y documentación oficial
Si quieres profundizar más en la personalización de bloques y bucles en WordPress, aquí tienes algunos recursos imprescindibles:
- Documentación oficial de WordPress sobre el bloque Query Loop
- Referencia completa de registerBlockVariation
- Hook pre_render_block
- Cómo añadir JavaScript a un plugin de WordPress
- Añadir archivos CSS y JS en WooCommerce
Todos estos enlaces te ayudarán a llevar tu desarrollo con bloques a un siguiente nivel, con total control tanto del backend como del frontend.



