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
| Argument | Description | Example |
|---|---|---|
post_type | Post type(s) | 'post', 'page', ['post', 'page'] |
posts_per_page | Number of posts (-1 for all) | 10 |
orderby | Sort field | 'date', 'title', 'menu_order', 'rand' |
order | Sort direction | 'ASC', 'DESC' |
post_status | Post status | 'publish', 'draft' |
category_name | Category slug | 'news' |
tag | Tag slug | 'featured' |
post__in | Include specific IDs | [1, 2, 3] |
post__not_in | Exclude specific IDs | [4, 5] |
author | Author ID | 1 |
s | Search 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
Blog with featured posts first
{# 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 %}
Related posts by category
{# 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
- Pagination — add page navigation
- Post provider — all fields available on post items
- get_terms() — query categories, tags, and custom taxonomies
- Variable block — store queries for pre-loop access