pollora/query
Pollora Query brings Laravel-style, fluent query building to WordPress. Compose WP_Query-powered post, term, and user queries through an expressive API, with clean integration for Pollora and Laravel applications. It supports familiar WordPress concepts while fitting neatly into Laravel projects.
Install the package and make sure WordPress is actually bootstrapped — this library wraps WP_Query, so it only works where WordPress core is loaded (a Pollora/WordPress app, a mu-plugin, or any file that has run wp-load.php).
composer require pollora/query
Look first at the builder class itself (the Query class) and its available methods. The fluent API intentionally mirrors WP_Query argument names, so anything you know from the WordPress docs (post_type, posts_per_page, meta_query, tax_query, orderby, …) translates directly into a chained call.
Your first use case is almost always replacing a block of array-based WP_Query code in a controller or view:
use Pollora\Query\Query;
$posts = Query::postType('post')
->postsPerPage(6)
->orderBy('date', 'DESC')
->get();
Then iterate the results, or call the underlying WP_Query if you need the standard Loop (have_posts() / the_post()). Start by converting one existing new WP_Query([...]) call — if the output is identical, the rest of your codebase can follow.
Controller/service composition. Build queries in your controller (or a dedicated repository/query object) rather than in Blade templates. Pass the resulting collection or WP_Query instance to the view:
public function index()
{
$news = Query::postType('news')
->postsPerPage(9)
->orderBy('date', 'DESC')
->get();
return view('news.index', compact('news'));
}
Reusable query scopes. Because it's fluent, you can extract common constraints into small helper methods that return the builder, keeping call sites readable:
protected function publishedNews(): Query
{
return Query::postType('news')
->postStatus('publish')
->orderBy('date', 'DESC');
}
$latest = $this->publishedNews()->postsPerPage(3)->get();
$archive = $this->publishedNews()->postsPerPage(12)->paged($page)->get();
Meta and taxonomy queries. Chain the nested arrays exactly as you would with WP_Query, but keep them near the top of the chain so the intent of the query reads top-down:
$featured = Query::postType('product')
->metaQuery([
['key' => 'is_featured', 'value' => '1', 'compare' => '='],
])
->taxQuery([
'category' => ['taxonomy' => 'category', 'field' => 'slug', 'terms' => 'featured'],
])
->get();
Pagination. Combine paged() with WordPress's paginate_links() or Laravel's paginator if you're building the markup yourself. Compute max_num_pages from the underlying query and pass it to your view helper.
Loop interop. When you need template tags (the_title(), the_content(), get_template_part()), get the underlying WP_Query from the builder and run a normal loop. This is the bridge that lets you use the package in a legacy theme without rewriting templates.
Dependency injection. If the package provides a facade or container binding, prefer injecting it into services so unit tests can swap it, rather than calling a static factory deep in your domain code.
WP_Query must not run before init. Don't resolve or execute a query in a service provider's register()/boot() — defer to a hook.get() vs the Loop. get() is convenient but does not set up the global $post and does not populate $wp_query. If you later call the_post() or template tags, call wp_reset_postdata() or use the underlying query object.posts_per_page (or the fluent postsPerPage) rather than perPage; -1 means "all posts". Watch out for numberposts vs posts_per_page semantics.relation key or a flat array where a nested list is expected is the most common silent failure. If a query returns everything or nothing unexpectedly, dump the generated args.noFoundRows() / no_found_rows => true for queries where you don't need pagination counts, and avoid -1 on large datasets. If you build many similar queries, wrap results in a transient or object cache keyed on the arguments.$query->request) and var_dump the final args to verify the fluent chain produced what you expect. A tiny "dump the args" helper saves a lot of guessing.WP_Post objects with raw content; escaping and apply_filters('the_content', ...) remain your responsibility in the view.->featured()) once in a service provider — that's a clean way to standardize query conventions across a team.How can I help you explore Laravel packages today?