PostQuery builds the arguments of WP_Query: posts, pages, attachments and custom post types.
Read Concepts first for how builders, defaults and errors work, and Getting results for first(), ids(), count() and exists(). Examples assume:
use Pollora\Query\PostQuery;- Defaults
- Selecting posts
- Post type, status and MIME type
- Authors
- Categories
- Tags
- Taxonomies
- Search
- Password
- Comment count
- Permission
- Dates
- Custom fields
- Limits and pagination
- Order
- Fields
- Cache and performance
PostQuery::select()->get();WP_Query receives:
[
'fields' => 'all',
'post_type' => 'any',
]post_typeis'any'(WordPress uses'post'), so pages and custom post types are included. CallpostType()to restrict it.'any'still leaves out the post types registered withexclude_from_search.fieldsis'all'.- Everything else is WordPress' default: for instance only
publishposts (and the private ones the current user can read) are returned, and the number of posts per page is the Posts per page setting.
| Method | WordPress argument | Notes |
|---|---|---|
PostQuery::find(5) |
p |
One post by ID. |
PostQuery::find([1, 2, 3]) |
post__in |
Several posts by ID. |
postId(int) |
p |
|
postSlug(string) |
name |
The slug of a post, or of a page. |
pageId(int) |
page_id |
|
pagename(string) |
pagename |
The slug of a page. Hierarchical paths work: 'parent/child'. |
title(string) |
title |
Exact post title. |
menuOrder(int) |
menu_order |
|
postParent(int) |
post_parent |
Children of a post (0: top-level only). |
whereParentIn(array) / whereParentNotIn(array) |
post_parent__in / post_parent__not_in |
|
whereIn(array) / whereNotIn(array) |
post__in / post__not_in |
Include / exclude by ID. |
slugIn(array) |
post_name__in |
Several slugs. |
PostQuery::select()->postType('page')->pagename('about/team')->get();WP_Query receives:
[
'fields' => 'all',
'pagename' => 'about/team',
'post_type' => 'page',
]An empty list is not "no result": whereIn([]) sends an empty post__in, which WordPress ignores, so every post matches. Guard an empty list yourself before building the query.
| Method | WordPress argument |
|---|---|
postType(string|array) |
post_type |
postStatus(string|array) |
post_status |
postMimeType(string|array) |
post_mime_type |
PostQuery::select()
->postType(['post', 'event'])
->postStatus(['publish', 'future'])
->get();WP_Query receives:
[
'fields' => 'all',
'post_type' => [
'post',
'event',
],
'post_status' => [
'publish',
'future',
],
]PostQuery::select()->postType('attachment')->postStatus('inherit')->postMimeType(['image/jpeg', 'image/png'])->get();WP_Query receives:
[
'fields' => 'all',
'post_mime_type' => [
'image/jpeg',
'image/png',
],
'post_type' => 'attachment',
'post_status' => 'inherit',
]| Method | WordPress argument |
|---|---|
author(int) |
author |
authorName(string) |
author_name (the user's nicename) |
authorIn(array) / authorNotIn(array) |
author__in / author__not_in |
PostQuery::select()->postType('post')->authorIn([1, 4])->get();WP_Query receives:
[
'author__in' => [
1,
4,
],
'fields' => 'all',
'post_type' => 'post',
]| Method | WordPress argument | Notes |
|---|---|---|
cat(int) |
cat |
|
categoryName(string) |
category_name |
The slug. |
categoryAnd(array) |
category__and |
In all these categories. |
categoryIn(array) |
category__in |
In any of them. |
categoryNotIn(array) |
category__not_in |
PostQuery::select()->postType('post')->categoryAnd([3, 7])->get();WP_Query receives:
[
'category__and' => [
3,
7,
],
'fields' => 'all',
'post_type' => 'post',
]| Method | WordPress argument | Notes |
|---|---|---|
tag(string) |
tag |
A slug. |
tagId(int) |
tag_id |
|
tagAnd(array) |
tag__and |
Tag IDs, all of them. |
tagIn(array) |
tag__in |
Tag IDs, any of them. |
tagNotIn(array) |
tag__not_in |
|
tagSlugAnd(array) |
tag_slug__and |
Tag slugs, all of them. |
tagSlugIn(array) |
tag_slug__in |
Tag slugs, any of them. |
taxQuery() filters on any taxonomy, with where() / orWhere() and nesting. It has its own page.
PostQuery::select()
->postType('product')
->taxQuery(function (TaxQuery $query) {
$query->where($query->taxonomy('product_cat')->searchByTermSlug()->contains(['shoes']));
})
->get();WP_Query receives:
[
'fields' => 'all',
'post_type' => 'product',
'tax_query' => [
'relation' => 'AND',
[
'taxonomy' => 'product_cat',
'field' => 'slug',
'terms' => [
'shoes',
],
'operator' => 'IN',
'include_children' => true,
],
],
]| Method | WordPress argument |
|---|---|
search(string) |
s |
exactSearch(bool = true) |
exact: match the whole keyword instead of a partial match |
searchSentence(bool = true) |
sentence: search the keyword as a phrase instead of splitting it into terms |
searchColumns(array) |
search_columns: among post_title, post_excerpt, post_content |
PostQuery::select()->search('summer sale')->searchSentence()->searchColumns(['post_title'])->get();WP_Query receives:
[
'fields' => 'all',
'post_type' => 'any',
's' => 'summer sale',
'sentence' => true,
'search_columns' => [
'post_title',
],
]| Method | WordPress argument |
|---|---|
withPassword() |
has_password => true: only password protected posts |
withoutPassword() |
has_password => false: only posts without a password |
withPassword('secret') |
post_password: posts protected by that exact password |
commentCount() takes a number (exactly that many comments) or WordPress' array form:
PostQuery::select()->commentCount(['value' => 10, 'compare' => '>='])->get();WP_Query receives:
[
'comment_count' => [
'value' => 10,
'compare' => '>=',
],
'fields' => 'all',
'post_type' => 'any',
]userPermission('readable' | 'editable') (perm) keeps the posts the current user can read, or edit. Any other value throws a QueryException.
Two ways to filter by date:
- Simple fields, for one exact value:
year(),month()(monthnum),day(),hour(),minute(),second(),week()(w, week of the year, 0 to 53) andyearMonth()(m, asYYYYMM). - A date query, for ranges, several conditions and other columns:
dateQuery(), documented in Date queries.
PostQuery::select()->postType('post')->year(2024)->month(3)->get();WP_Query receives:
[
'fields' => 'all',
'year' => 2024,
'monthnum' => 3,
'post_type' => 'post',
]Three levels, from simplest to most flexible:
- Top-level arguments:
metaKey(),metaValue(),metaCompare(),metaType()and, for the key itself,metaCompareKey()andmetaTypeKey()(meta_key,meta_value,meta_compare,meta_type,meta_compare_key,meta_type_key). metaQuery(), for several conditions andOR: Meta queries.orderByMeta(), to sort by a custom field (see Order).
PostQuery::select()->postType('product')->metaKey('color')->metaValue('red')->get();WP_Query receives:
[
'fields' => 'all',
'meta_key' => 'color',
'meta_value' => 'red',
'post_type' => 'product',
]| Method | WordPress argument | Notes |
|---|---|---|
limit(int), take(int), postsPerPage(int) |
posts_per_page |
Three names for the same argument. -1 is "all". |
noPaging(bool = true) |
nopaging |
Return all posts. |
offset(int), skip(int) |
offset |
When set, WordPress ignores paged. Use one or the other. |
paged(int) |
paged |
The page of the archive. |
page(int) |
page |
The page of a paginated static front page (<!--nextpage-->). Not the same as paged(). |
postsPerArchivePage(int) |
posts_per_archive_page |
Only applies to archive pages. |
ignoreStickyPosts(bool = true) |
ignore_sticky_posts |
Stop putting sticky posts first. |
PostQuery::select()->postType('post')->take(10)->paged(3)->ignoreStickyPosts()->get();WP_Query receives:
[
'fields' => 'all',
'posts_per_page' => 10,
'paged' => 3,
'ignore_sticky_posts' => true,
'post_type' => 'post',
]orderBy($field, $direction = 'DESC') adds a sort key; call it several times to sort by several fields, in call order. It also takes a map of fields and directions. orderby is always sent as an array.
| Method | Meaning |
|---|---|
orderBy('title', 'ASC') |
Sort by a field: post_date, title, menu_order, comment_count, post__in, a named meta clause… |
orderBy(['post_date' => 'DESC', 'title' => 'ASC']) |
Several fields at once. |
latest() |
post_date descending. |
oldest() |
post_date ascending. |
inRandomOrder() |
rand. It replaces any other sort key, and a later orderBy() replaces it. |
orderByMeta($key, $direction = 'DESC', $numeric = false) |
Sort by a custom field (meta_value, or meta_value_num when $numeric). |
PostQuery::select()->postType('event')->orderBy('menu_order', 'ASC')->orderBy('title', 'ASC')->get();WP_Query receives:
[
'fields' => 'all',
'orderby' => [
'menu_order' => 'ASC',
'title' => 'ASC',
],
'post_type' => 'event',
]orderByMeta() also sets meta_key, and WordPress then only returns posts that have that key.
PostQuery::select()->postType('product')->orderByMeta('price', 'ASC', numeric: true)->get();WP_Query receives:
[
'fields' => 'all',
'meta_key' => 'price',
'orderby' => [
'meta_value_num' => 'ASC',
],
'post_type' => 'product',
]To sort by a clause of a meta query, name it with state() and use that name in orderBy(): see Meta queries.
An invalid direction throws a QueryException.
fields() (or PostQuery::select('ids')) is 'all', 'ids' or 'id=>parent'. See Getting results.
| Method | WordPress argument | Notes |
|---|---|---|
cacheResults(bool = true) |
cache_results |
|
updateMetaCache(bool = true) |
update_post_meta_cache |
Pass false if you never read the posts' custom fields. |
updateTermCache(bool = true) |
update_post_term_cache |
Pass false if you never read the posts' terms. |
lazyLoadTermMeta(bool = true) |
lazy_load_term_meta |
|
noFoundRows(bool = true) |
no_found_rows |
Skips counting the total: faster, but found_posts and max_num_pages are 0. |
suppressFilters(bool = true) |
suppress_filters |
Turning a cache off is only faster if you do not read what it holds: a get_post_meta() in the loop then costs one query per post. For the common cases, the terminal methods already do the right thing: first(), ids() and exists() skip the total count, and ids() fetches IDs only.
PostQuery::select()->postType('post')->noFoundRows()->updateMetaCache(false)->updateTermCache(false)->get();WP_Query receives:
[
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
'no_found_rows' => true,
'fields' => 'all',
'post_type' => 'post',
]