DTO-first PHP 8.5 SDK for the WordPress REST API, built for typed content automation, pagination, media, settings, users, taxonomies, discovery, and custom/plugin endpoints.
Package name: jooservices/wordpress-sdk
composer require jooservices/wordpress-sdkuse JOOservices\WordPress\Sdk\Data\Query\ListPostsQuery;
use JOOservices\WordPress\Sdk\WordPressService;
$wordpress = WordPressService::create(
baseUrl: getenv('WORDPRESS_URL'),
username: getenv('WORDPRESS_USER'),
password: getenv('WORDPRESS_APP_PASSWORD'),
);
$posts = $wordpress->posts()->list(new ListPostsQuery(
perPage: 10,
search: 'automation',
fields: 'id,title,link,date',
embed: true,
));
foreach ($posts as $post) {
echo $post->title->rendered . PHP_EOL;
}| Endpoint | Service | Status |
|---|---|---|
| Posts | posts() |
CRUD |
| Pages | pages() |
CRUD |
| Media | media() |
list/get/upload/delete |
| Users | users() |
CRUD + me() |
| Comments | comments() |
CRUD |
| Categories | categories() |
CRUD |
| Tags | tags() |
CRUD |
| Search | search() |
list/search |
| Taxonomies | taxonomies() |
list/get |
| Post types | postTypes() |
list/get |
| Statuses | statuses() |
list/get |
| Application Passwords | applicationPasswords() |
list/get/create/delete/deleteAll |
| Settings | settings() |
get/update |
| Discovery / schema | discovery() |
index/routes/schema |
| Custom endpoints | custom() |
GET/POST/PUT/PATCH/DELETE raw arrays |
| Revisions | revisions() |
posts/pages/block autosaves |
| Plugins | plugins() |
raw admin operations |
| Themes | themes() |
raw admin reads |
| Blocks / block types / renderer / directory | blocks(), blockTypes(), blockRenderer(), blockDirectory() |
raw editor operations |
| Menus / navigation | menuLocations(), navigations(), navMenus(), navMenuItems() |
raw navigation operations |
| Templates / template parts / global styles | templates(), templateParts(), globalStyles() |
raw block theme operations |
| Widgets / sidebars | widgets(), widgetTypes(), sidebars() |
raw widget operations |
| Site Health | siteHealth() |
raw diagnostic test reads |
The SDK also ships with optional developer-experience helpers under JOOservices\WordPress\Sdk\Support.
PostBuilderhelps assemble post payloads fluently before callingposts()->create()orupdate().ContentBuilderhelps generate Gutenberg-compatible block markup in PHP.PostTemplate, concrete templates, andcreateFromTemplate()are template helpers built on top of normal post creation.
These helpers are not native WordPress REST API resources. They are kept as SDK extras because they reduce repetitive WordPress payload-building while staying generic to SDK users.
The Docker integration suite verifies these helpers against a real local WordPress REST API. composer test:wordpress starts Docker services, installs WordPress with WP-CLI, creates test auth, uploads local media fixtures, and runs the main integration tests. Optional WordPress capabilities that need extra plugins, block registration, or block-theme state live in the independent composer test:wordpress:extended suite. No manual WordPress setup is required; use composer test:wordpress:reset to wipe the disposable database and uploads volume.
The SDK accepts either raw query arrays or typed query DTOs for list and read operations.
use JOOservices\WordPress\Sdk\Data\Query\ListCommentsQuery;
$comments = $wordpress->comments()->list(new ListCommentsQuery(
post: 42,
status: 'approve',
perPage: 25,
fields: 'id,content,date',
));WordPress pagination headers are exposed through PaginatedCollection. List-style services provide auto-pagination helpers where WordPress returns paginated collections.
$media = $wordpress->media()->list(['per_page' => 20, 'page' => 2]);
printf(
"Loaded %d items out of %d across %d pages\n",
count($media),
$media->total,
$media->totalPages,
);foreach ($wordpress->posts()->cursor(['per_page' => 50]) as $post) {
// Streams one page at a time.
}
$wordpress->posts()->each(function ($post): bool {
return $post->id !== 123; // return false to stop early
}, ['status' => 'publish']);Use all() only when loading every matching post into memory is acceptable.
$settings = $wordpress->settings()->get();
$wordpress->settings()->update(['title' => 'New title']);
$routes = $wordpress->discovery()->routes();
$schema = $wordpress->discovery()->schema('/wp/v2/posts');
$items = $wordpress->custom()->get('/my-plugin/v1/items', ['page' => 1]);
$created = $wordpress->custom()->post('/my-plugin/v1/items', ['name' => 'Example']);Custom endpoint paths are relative to the configured WordPress REST API root. Full external URLs are rejected.
$password = $wordpress->applicationPasswords()->create('me', [
'name' => 'Publishing Worker',
]);WordPress returns the raw generated application password only in the create response. Store it immediately in a secret manager and never log it.
Broad WordPress admin/editor endpoint groups are exposed as raw arrays until their response schemas prove stable enough for public DTO contracts.
$plugins = $wordpress->plugins()->list();
$themes = $wordpress->themes()->list();
$revisions = $wordpress->revisions()->posts(123)->list();
$rendered = $wordpress->blockRenderer()->render('core/latest-posts', ['postsToShow' => 3]);
$templates = $wordpress->templates()->list();
$health = $wordpress->siteHealth()->backgroundUpdates();Most of these endpoints require authenticated users with admin/editor capabilities. Block renderer requests are sent with WordPress editor context because the REST renderer endpoint validates dynamic blocks against editor-only route context.
use JOOservices\WordPress\Sdk\Exceptions\UnauthorizedException;
use JOOservices\WordPress\Sdk\Exceptions\WordPressApiException;
try {
$wordpress->posts()->get(123, ['context' => 'edit']);
} catch (UnauthorizedException $exception) {
// Refresh credentials or verify the app password.
} catch (WordPressApiException $exception) {
$payload = $exception->toArray(); // sanitized diagnostic payload
}Start with:
- Documentation hub
- Architecture overview
- Installation
- Quick start
- User guide
- Development
- Maintenance risks and gaps
composer lint
composer lint:all
composer test
composer test:integration
composer test:wordpress
composer test:wordpress:extended
composer test:wordpress:reset
composer test:integration:docker
composer test:coverage
composer test:coverage:gate
composer test:coverage-map
composer quality
composer check
composer ciRun composer test:wordpress for the one-command Docker/WP-CLI WordPress integration flow. Run composer test:wordpress:extended after it, or by itself, for optional capabilities such as deterministic plugin reads, dynamic block rendering, global styles, navigation/template routes, site health reads, and the chaptered story template workflow with uploaded media. See Testing for details, safety guards, reset commands, and route skip policy.
composer test:coverage now includes a Clover XML audit gate. Aggregate statement coverage must stay at or above 90%, and no coverable production file, class, or method in src/ may remain at 0% coverage unless it has a documented exclusion reason in tools/test-coverage-gate.php.
Use WordPress application passwords through environment variables. Do not commit live credentials, and do not log authorization headers or raw secrets. See SECURITY.md.