Skip to main content

Query Posts

This page covers how to query posts, pages, and custom post types using get_posts().

Arguments follow the WP_Query API — any argument WP_Query accepts, get_posts() accepts too.

Basic usage

{% for post in get_posts({'post_type': 'post', 'posts_per_page': 6}) %}
<h2>{{ post.title }}</h2>
<p>{{ post.excerpt }}</p>
{% endfor %}

Common arguments

ArgumentDescriptionExample
post_typePost type(s)'post', 'page', ['post', 'page']
posts_per_pageNumber of posts (-1 for all)10
orderbySort field'date', 'title', 'menu_order', 'rand'
orderSort direction'ASC', 'DESC'
post_statusPost status'publish', 'draft'
category_nameCategory slug'news'
tagTag slug'featured'
post__inInclude specific IDs[1, 2, 3]
post__not_inExclude specific IDs[4, 5]
authorAuthor ID1
sSearch term'keyword'

Main query inheritance

Use posts (without get_posts()) to inherit the main WordPress query. This is how you build archive templates, search results, and custom post type archives:

{% for post in posts %}
<h2>{{ post.title }}</h2>
<p>{{ post.excerpt }}</p>
{% endfor %}

The main query is cloned, so other blocks can still use it independently.

Taxonomy filtering

By category or tag slug

{% for post in get_posts({'post_type': 'post', 'category_name': 'news'}) %}
<h2>{{ post.title }}</h2>
{% endfor %}

{% for post in get_posts({'post_type': 'post', 'tag': 'featured'}) %}
<h2>{{ post.title }}</h2>
{% endfor %}

Multiple categories (OR)

{% for post in get_posts({'post_type': 'post', 'category_name': 'news,updates'}) %}
<h2>{{ post.title }}</h2>
{% endfor %}

Complex taxonomy query

Use tax_query for advanced filtering — multiple taxonomies, AND/OR logic:

{# Posts in "news" OR "updates" category #}
{% for post in get_posts({
'post_type': 'post',
'tax_query': [{'taxonomy': 'category', 'field': 'slug', 'terms': ['news', 'updates']}]
}) %}

{# Products in "shoes" category AND "sale" tag #}
{% for post in get_posts({
'post_type': 'product',
'tax_query': {
'relation': 'AND',
'0': {'taxonomy': 'product_cat', 'field': 'slug', 'terms': 'shoes'},
'1': {'taxonomy': 'product_tag', 'field': 'slug', 'terms': 'sale'}
}
}) %}

Meta filtering

Simple meta value

{# Featured products #}
{% for post in get_posts({
'post_type': 'product',
'meta_query': [{'key': 'featured', 'value': '1'}]
}) %}
<div class="product featured">{{ post.title }}</div>
{% endfor %}

Numeric comparison

{# Products over 100€ #}
{% for post in get_posts({
'post_type': 'product',
'meta_query': [{'key': 'price', 'value': 100, 'compare': '>', 'type': 'NUMERIC'}]
}) %}
<div class="product">
{{ post.title }}{{ post.meta('price')|number_format(2) }}
</div>
{% endfor %}

Multiple meta conditions

{# Products on sale AND in stock #}
{% for post in get_posts({
'post_type': 'product',
'meta_query': {
'relation': 'AND',
'0': {'key': 'on_sale', 'value': '1'},
'1': {'key': 'stock', 'value': 0, 'compare': '>', 'type': 'NUMERIC'}
}
}) %}
<div class="product">{{ post.title }}</div>
{% endfor %}

Ordering

By title

{% for post in get_posts({'post_type': 'page', 'orderby': 'title', 'order': 'ASC'}) %}

By menu order (pages)

{% for post in get_posts({'post_type': 'page', 'orderby': 'menu_order', 'order': 'ASC'}) %}

Random posts

{% for post in get_posts({'post_type': 'post', 'orderby': 'rand', 'posts_per_page': 3}) %}

By meta value

{# Events sorted by date #}
{% for post in get_posts({
'post_type': 'event',
'meta_key': 'event_date',
'orderby': 'meta_value',
'order': 'ASC'
}) %}
<div class="event">
{{ post.title }}{{ post.meta('event_date')|date('F j, Y') }}
</div>
{% endfor %}

By numeric meta value

{# Products sorted by price, cheapest first #}
{% for post in get_posts({
'post_type': 'product',
'meta_key': 'price',
'orderby': 'meta_value_num',
'order': 'ASC'
}) %}

Real-world patterns

{# Sticky/featured posts first, then latest #}
{% for post in get_posts({
'post_type': 'post',
'posts_per_page': 12,
'ignore_sticky_posts': false
}) %}
<article class="{{ post.meta('featured') ? 'card featured' : 'card' }}">
<img src="{{ post.thumbnail.src('medium') ?? '/img/placeholder.jpg' }}" alt="{{ post.thumbnail.alt ?? post.title }}">
<h2>{{ post.title }}</h2>
<p>{{ post.excerpt({words: 20}) }}</p>
</article>
{% endfor %}
{# Posts in the same category, excluding current post #}
{% for related in get_posts({
'post_type': 'post',
'category_name': post.categories[0].slug,
'post__not_in': [post.id],
'posts_per_page': 3
}) %}
<a href="{{ related.link }}">{{ related.title }}</a>
{% endfor %}

WooCommerce product grid

{% set products = get_posts({
'post_type': 'product',
'posts_per_page': 12,
'tax_query': [{'taxonomy': 'product_cat', 'field': 'slug', 'terms': 'clothing'}]
}) %}

<p>{{ products.found_posts }} products</p>

{% for product in products %}
<div class="product-card">
<img src="{{ product.thumbnail.src('medium') }}" alt="{{ product.thumbnail.alt ?? product.title }}">
<h3>{{ product.title }}</h3>
<span class="price">{{ product.price|number_format(2) }} €</span>
</div>
{% endfor %}

{{ products.pagination }}

Child pages of the current page

{% for post in get_posts({
'post_type': 'page',
'post_parent': post.id,
'orderby': 'menu_order',
'order': 'ASC',
'posts_per_page': -1
}) %}
<a href="{{ post.link }}">{{ post.title }}</a>
{% endfor %}

Search results

{% set results = get_posts({'s': request.get('s'), 'posts_per_page': 10}) %}

<h1>Search results for "{{ request.get('s') }}" ({{ results.found_posts }})</h1>

{% for post in results %}
<article>
<h2><a href="{{ post.link }}">{{ post.title }}</a></h2>
<p>{{ post.excerpt({words: 30}) }}</p>
</article>
{% endfor %}

{{ results.pagination }}
Common mistake

posts_per_page: -1 returns every matching post. On a site with thousands of posts, this slows down the page significantly. Always set a reasonable limit unless you truly need all results.

Next steps