pollora/wordpress-args
A fluent PHP API for building WordPress arguments without messy arrays. Compose WP_Query, register_post_type, taxonomy, and similar configuration through chainable methods, keeping WordPress setup cleaner, more readable, and easier to maintain in Laravel or standalone PHP projects.
Install
composer require pollora/wordpress-args
There is no service provider and no config to publish — it is a plain value-object library that turns the associative arrays WordPress expects into fluent, typed builders. That is the whole mental model: build an object, call toArray(), hand the array to WordPress.
Where to look first
src/ — the class names map 1:1 to WordPress APIs (PostType, Taxonomy, WPQuery, meta box / block args, etc.). Read the base/abstract argument class first; every concrete class inherits the same toArray() / setter conventions, so learning one teaches you all of them.tests/ — this is where the real API documentation lives for a 2-star package. The tests show the exact method names and the exact array shape produced.illuminate/support (Arrayable, Collection), because that dictates whether you can use it outside a Laravel app.First use case
Take the most painful array literal in your codebase — the register_post_type() call — and convert it:
// Before
register_post_type('book', [
'labels' => ['name' => 'Books', 'singular_name' => 'Book'],
'public' => true,
'has_archive' => true,
'supports' => ['title', 'editor', 'thumbnail', 'excerpt'],
'rewrite' => ['slug' => 'books', 'with_front' => false],
'show_in_rest' => true,
]);
// After
$book = PostType::make('book')
->label('Books', 'Book')
->public()
->hasArchive()
->supports('title', 'editor', 'thumbnail', 'excerpt')
->rewrite(slug: 'books', withFront: false)
->showInRest();
register_post_type($book->name(), $book->toArray());
If that reads better to your team, adopt it everywhere. If it does not, stop here — this package has no other value proposition, and you should not adopt it anyway.
The single biggest win is moving post types, taxonomies, and meta registrations out of functions.php/Plugin.php into dedicated classes or a definitions directory, then registering them in a loop:
// app/WordPress/PostTypes/Book.php
final class Book
{
public static function args(): PostType
{
return PostType::make('book')
->label('Books', 'Book')
->public()
->hasArchive()
->supports('title', 'editor', 'thumbnail')
->showInRest()
->rewrite(slug: 'books');
}
}
// registration
foreach ([Book::class, Author::class] as $type) {
$args = $type::args();
register_post_type($args->name(), $args->toArray());
}
This gives you a single place to diff when WordPress adds an argument, and makes the definitions greppable.
Because setters return $this, you can branch mid-chain without building temporary arrays. This is where the object model beats arrays most clearly:
$args = PostType::make('event')
->label('Events', 'Event')
->public()
->supports('title', 'editor');
if (is_multisite()) {
$args->showInRest();
}
if (wp_get_environment_type() !== 'production') {
$args->rewrite(slug: 'events-dev');
}
register_post_type($args->name(), $args->toArray());
With raw arrays you would end up with array_merge soup and lose ordering guarantees for supports.
WP_Query wrapping for repeated query shapesUse the query builder for named, reusable queries only. Ad-hoc queries inside a template should stay as arrays — otherwise you are paying ceremony for nothing.
$featured = WPQuery::make()
->postType('post')
->postsPerPage(3)
->metaKey('featured')
->metaValue('1')
->orderBy('date', 'DESC');
$loop = new WP_Query($featured->toArray());
A useful convention: expose these as static factory methods on a Queries class (Queries::featuredPosts(), Queries::upcomingEvents()) so query intent has a name.
If you are on Pollora or a Laravel-flavoured WordPress stack, resolve definitions from the container rather than calling statics, so a site-specific plugin can override a definition:
$this->app->bind(Book::class, fn () => Book::args());
Then loop over an injected iterable of argument objects. This is the pattern that makes the package worthwhile on multi-site or white-label installs where the same codebase ships with different post types.
toArray() at the boundaryKeep the discipline: objects inside your code, arrays at the WordPress boundary. Never pass the object itself to a WordPress function and never let the object leak into templates. Every register_* / new WP_Query call should be a ->toArray() call, which makes it trivially searchable when you need to audit all WordPress registrations.
The main testability benefit is that you can assert on the produced array without booting WordPress:
$args = Book::args()->toArray();
$this->assertTrue($args['show_in_rest']);
$this->assertSame('books', $args['rewrite']['slug']);
$this->assertContains('thumbnail', $args['supports']);
Write one such test per definition. It is cheap and catches the "someone removed show_in_rest and the block editor broke" class of regression.
It is a thin wrapper, not a validator. Nothing stops you from setting an argument WordPress does not understand for your installed version, or omitting a required one. register_post_type() will still silently register something half-broken. Keep a test that asserts the key set, and check the WordPress version requirement of the package against your minimum supported WP.
Nested arguments are the sharp edge. labels, capabilities, rewrite, and supports are where array literals actually hurt, and they are also the most likely place for a fluent API to be incomplete or opinionated. Before adopting, verify the nested shapes you need (rewrite => ['with_front' => false, 'pages' => true], custom capabilities with map_meta_cap) are reachable. If they are not, fall back to a raw array merge for that key rather than fighting the builder.
(array) is not toArray(). Casting the object will expose internal properties, not WordPress arguments. Always call toArray(). Add a PHPStan rule or code review checklist item if you can.
Ordering matters for supports and labels. Fluent accumulation preserves insertion order, which is good — but be aware that calling supports() twice appends rather than replaces. Decide whether you want append or replace semantics and document it in your own wrapper; do not assume the package chose the one you wanted.
Booleans vs. strings. WordPress historically accepts 'true'/'false' strings for some args and real booleans for others, and a wrapper that type-hints bool can produce output that behaves differently from the array you used to write. Diff the produced array against your old literal during migration — literally var_export both and compare — before shipping.
2 stars and 0 dependents means you are the QA. Pin the version in composer.json ("pollora/wordpress-args": "^1.0" or an exact tag), read the changelog before every update, and be prepared to vendor-fork it. It is MIT and small, so forking is cheap; treat your usage as "we own this code now."
Extension points. The base argument class is where to hook in if you need site-wide defaults — e.g. a LocalizedPostType subclass that automatically applies show_in_rest and a capabilities map. Prefer one such subclass over sprinkling ->showInRest() across twenty definitions. Do not monkey-patch; extend.
Debugging. When a registration misbehaves, dump the array and compare against the WordPress developer reference for that function, not against the package docs. The package docs will always lag the WordPress handbook, and the handbook is the source of truth for what the array should contain.
Do not over-apply it. Block attributes, register_block_type args, and simple option arrays are often clearer as literal arrays. Reserve the object model for the handful of large, conditional, reused argument sets — post types, taxonomies, and repeatable queries.
How can I help you explore Laravel packages today?