DesenvolvedorWordPress WhatsApp

WP_Query e a Loop: consultas avançadas no WordPress

A WP_Query é o coração de toda consulta de conteúdo no WordPress. Entenda como funciona a Loop, os principais argumentos, meta_query e tax_query, como alterar a consulta principal com pre_get_posts e como não derrubar a performance do site.

Precisa de consultas sob medida?

Atualizado em 19/06/2026

Toda página do WordPress — a home, uma categoria, a busca, um post — é, no fundo, o resultado de uma consulta ao banco encapsulada pela classe WP_Query. Entender essa classe e o laço que percorre os resultados (a Loop) é o que separa quem "mexe no tema" de quem realmente programa para WordPress. Neste artigo vamos da Loop básica até meta_query, tax_query, modificação da consulta principal com pre_get_posts e os cuidados de performance que evitam derrubar o site.

O que é a Loop

A Loop (laço) é o trecho de código que percorre os posts retornados por uma consulta e os exibe. Ela se apoia em duas funções:

Na consulta principal de um tema, a Loop é simplesmente:

<?php
if ( have_posts() ) :
    while ( have_posts() ) :
        the_post();
        the_title( '<h2>', '</h2>' );
        the_excerpt();
    endwhile;
else :
    echo '<p>Nenhum conteúdo encontrado.</p>';
endif;

Repare que aqui não criamos nenhum objeto: have_posts() e the_post() operam sobre o $wp_query global, a consulta que o WordPress montou para a URL atual.

WP_Query: criando uma consulta secundária

Quando você precisa de uma lista além do conteúdo principal — os 5 posts mais recentes em uma sidebar, produtos em destaque, posts relacionados — instancia a sua própria WP_Query. O fluxo é sempre o mesmo: montar os argumentos, percorrer com a Loop e, ao final, resetar o post global.

<?php
$args = array(
    'post_type'      => 'post',
    'posts_per_page' => 5,
    'orderby'        => 'date',
    'order'          => 'DESC',
    'no_found_rows'  => true, // não há paginação aqui
);

$consulta = new WP_Query( $args );

if ( $consulta->have_posts() ) :
    echo '<ul class="recentes">';
    while ( $consulta->have_posts() ) :
        $consulta->the_post();
        printf(
            '<li><a href="%s">%s</a></li>',
            esc_url( get_permalink() ),
            esc_html( get_the_title() )
        );
    endwhile;
    echo '</ul>';
endif;

wp_reset_postdata(); // OBRIGATÓRIO após uma WP_Query secundária

Esqueceu o wp_reset_postdata()? Como the_post() sobrescreve o $post global, todo o resto da página (comentários, template tags, outros blocos) passa a enxergar o último post da sua consulta, e não o post da página. É a causa nº 1 de "o título do post está errado depois da minha lista".

Principais argumentos

A WP_Query aceita dezenas de argumentos. Estes são os que você mais vai usar no dia a dia:

ArgumentoPara que serve
post_typeTipo de conteúdo: 'post', 'page', um CPT, ou um array de vários.
posts_per_pageQuantos posts por página. Use um teto; evite -1 (todos).
pagedPágina atual da paginação. Normalmente get_query_var( 'paged' ).
orderbyCritério de ordenação: date, title, menu_order, meta_value_num, rand...
orderDireção: ASC ou DESC.
post_statusStatus: publish (padrão no front), draft, pending...
meta_queryFiltra por campos personalizados (post meta). Veja abaixo.
tax_queryFiltra por termos de taxonomia (categorias, tags, taxonomias próprias).
no_found_rowstrue pula a contagem total de posts (mais rápido sem paginação).
fields'ids' retorna só os IDs — útil e leve quando você não precisa do objeto inteiro.

meta_query e tax_query

Os dois argumentos mais poderosos da WP_Query permitem filtrar por campos personalizados (meta_query) e por termos de taxonomia (tax_query). Ambos aceitam uma chave relation (AND ou OR) e podem ser combinados. O exemplo abaixo busca os imovel à venda, com preço de até 500 mil, na categoria/taxonomia "apartamento":

<?php
$args = array(
    'post_type'      => 'imovel',
    'posts_per_page' => 12,
    'meta_query'     => array(
        'relation' => 'AND',
        array(
            'key'   => 'situacao',
            'value' => 'venda',
        ),
        array(
            'key'     => 'preco',
            'value'   => 500000,
            'type'    => 'NUMERIC',
            'compare' => '<=',
        ),
    ),
    'tax_query'      => array(
        array(
            'taxonomy' => 'tipo_imovel',
            'field'    => 'slug',
            'terms'    => 'apartamento',
        ),
    ),
    // ordena pelo valor numérico do meta 'preco'
    'meta_key'       => 'preco',
    'orderby'        => 'meta_value_num',
    'order'          => 'ASC',
);

$imoveis = new WP_Query( $args );
// ... Loop com $imoveis->have_posts() ...
wp_reset_postdata();

Em meta_query, sempre informe 'type' => 'NUMERIC' ao comparar números: por padrão o valor é tratado como string e '9' > '50' sai verdadeiro. E lembre-se de que meta_query gera JOINs na tabela postmeta — muitas condições deixam a consulta cara. Para campos que você filtra com frequência, considere uma taxonomia (indexada) em vez de post meta.

Modificando a consulta PRINCIPAL: pre_get_posts

E quando você quer mudar a própria consulta da página — por exemplo, exibir 12 posts por página no arquivo, ou incluir um CPT na home? A resposta correta é o hook pre_get_posts, que recebe o objeto WP_Query antes de a consulta ir ao banco. Você ajusta os argumentos via $query->set() — sem refazer nada, sem quebrar a paginação:

<?php
add_action( 'pre_get_posts', 'meu_ajuste_consulta_principal' );

function meu_ajuste_consulta_principal( $query ) {
    // Nunca toque nas consultas do admin nem nas secundárias
    if ( is_admin() || ! $query->is_main_query() ) {
        return;
    }

    // Só no arquivo de categoria
    if ( $query->is_category() ) {
        $query->set( 'posts_per_page', 12 );
        $query->set( 'orderby', 'title' );
        $query->set( 'order', 'ASC' );
    }

    // Inclui o CPT 'livro' nos resultados da home, junto com posts
    if ( $query->is_home() ) {
        $query->set( 'post_type', array( 'post', 'livro' ) );
    }
}

Nunca use query_posts(). Ela substitui o $wp_query global, refaz a consulta do zero (duas queries em vez de uma) e quebra a paginação e o objeto da página. Não há como revertê-la com segurança. Para alterar a consulta principal use pre_get_posts; para listas adicionais, new WP_Query() + wp_reset_postdata().

As duas verificações — is_admin() e is_main_query() — são essenciais. Sem elas, o hook também alteraria as consultas do painel administrativo e qualquer WP_Query secundária do site, gerando bugs difíceis de rastrear.

Paginação correta em consultas secundárias

Se a sua WP_Query precisa paginar (uma página de template com listagem própria), passe o paged explicitamente e use paginate_links():

<?php
$paged = max( 1, get_query_var( 'paged' ), get_query_var( 'page' ) );

$q = new WP_Query( array(
    'post_type'      => 'post',
    'posts_per_page' => 10,
    'paged'          => $paged,
) );

// ... Loop ...

echo paginate_links( array(
    'total'   => $q->max_num_pages,
    'current' => $paged,
) );

wp_reset_postdata();

Performance: peça só o que vai usar

Consultas mal calibradas são uma das maiores causas de lentidão. Alguns princípios:

<?php
// Consulta enxuta: só IDs, sem contagem total, sem caches desnecessários
$ids = get_posts( array(
    'post_type'              => 'produto',
    'posts_per_page'         => 50,
    'fields'                 => 'ids',
    'no_found_rows'          => true,
    'update_post_meta_cache' => false,
    'update_post_term_cache' => false,
) );
// $ids agora é um array de IDs, pronto para usar

get_posts() é um wrapper sobre a WP_Query que já retorna o array de posts e ativa no_found_rows por padrão (pula a contagem total) — ótimo para listas simples. Por baixo, é a mesma classe. E como ele não usa the_post(), não exige wp_reset_postdata() (a menos que você chame setup_postdata() manualmente).

Consultas bem feitas, hooks no lugar certo e cache são o tripé da performance no WordPress. Antes de subir qualquer consulta para produção, valide também a segurança: dados vindos da URL (como $_GET) precisam ser sanitizados antes de entrar nos argumentos da WP_Query.

Consultas lentas ou listagens que não saem como você quer?

Escrevo WP_Query, meta_query e tax_query otimizadas, ajusto a consulta principal com pre_get_posts e cuido da performance. Me chame no WhatsApp.

WhatsApp: (43) 99932-9697

ou e-mail: [email protected]

Perguntas frequentes

Qual a diferença entre a Loop principal e uma WP_Query secundária?

A Loop principal é a consulta que o WordPress executa automaticamente para cada URL, com base no objeto global $wp_query — é ela que decide quais posts a página exibe. Uma WP_Query secundária é uma instância criada por você (new WP_Query()) para buscar conteúdo adicional, como uma lista de "posts relacionados". Para alterar a principal, use o hook pre_get_posts; para listas extras, crie uma WP_Query e sempre chame wp_reset_postdata() depois.

Por que não devo usar query_posts()?

Porque query_posts() substitui a consulta principal e refaz a query do zero, quebrando a paginação, o objeto da página e a performance. Não dá para reverter de forma confiável. Para modificar a consulta principal, o caminho correto é o hook pre_get_posts. Para consultas adicionais, use new WP_Query() seguido de wp_reset_postdata(). Considere query_posts() proibido em código de produção.

Quando preciso chamar wp_reset_postdata()?

Sempre que você roda uma WP_Query secundária e usa the_post() (ou template tags como the_title()) dentro dela. O the_post() sobrescreve o post global $post; sem o reset, o restante da página passa a enxergar o último post da sua consulta, não o post correto. Chame wp_reset_postdata() logo após o endwhile.

posts_per_page => -1 é perigoso?

Sim, em escala. -1 traz todos os posts que casam com a query, sem limite. Em um site com milhares de registros isso consome memória, sobrecarrega o banco e pode estourar o limite de memória do PHP. Defina sempre um teto realista (posts_per_page => 50, por exemplo) e pagine quando precisar de mais.

Como deixar uma WP_Query mais rápida?

Peça só o que vai usar. Se não há paginação, defina 'no_found_rows' => true para o WordPress pular a contagem total (o SQL_CALC_FOUND_ROWS). Se não vai ler meta nem termos no loop, use 'update_post_meta_cache' => false e 'update_post_term_cache' => false. Evite posts_per_page => -1 e combine com cache de objeto/transients para consultas caras.

Posso usar WP_Query no front-end e no admin?

Sim. A classe funciona em qualquer contexto. No hook pre_get_posts, porém, tenha cuidado: ele dispara para todas as consultas, inclusive as do painel. Sempre proteja a alteração com if ( is_admin() ) return; e $query->is_main_query() para não bagunçar listas do wp-admin nem consultas secundárias.

Referências oficiais

  1. WordPress.org — Class WP_Query (Code Reference)
  2. WordPress.org — pre_get_posts (Code Reference)
  3. WordPress.org — wp_reset_postdata() (Code Reference)
  4. WordPress.org — get_posts() (Code Reference)
  5. WordPress.org — Code Reference
Tem alguma dúvida? Ficarei feliz em ajudar. Clique Aqui