Skip to main content

Quick Start

Minimal router

use BetterRoute\Router\Router;

add_action('rest_api_init', function (): void {
Router::make('better-route', 'v1')
->get('/ping', fn (): array => ['pong' => true])
->meta([
'operationId' => 'systemPing',
'tags' => ['System'],
])
->permission(static fn (): bool => true)
;
});

Result:

  • namespace: better-route/v1
  • endpoint: GET /wp-json/better-route/v1/ping

Minimal resource (CPT)

use BetterRoute\Resource\Resource;

add_action('rest_api_init', function (): void {
Resource::make('articles')
->restNamespace('better-route/v1')
->sourceCpt('post')
->allow(['list', 'get'])
->fields(['id', 'title', 'slug', 'date'])
->filters(['status'])
->sort(['date', 'id'])
->register();
});

Minimal OpenAPI export endpoint

use BetterRoute\OpenApi\OpenApiRouteRegistrar;

OpenApiRouteRegistrar::register(
restNamespace: 'better-route/v1',
contractsProvider: static fn (): array => [
// typically merge router/resource contracts here
],
options: [
'title' => 'better-route API',
'version' => 'v0.4.0',
'serverUrl' => '/wp-json',
// Override the admin-only default (introduced in v0.3.0) to expose the doc:
// 'permissionCallback' => static fn (): bool => true,
]
);

The openapi.json endpoint is admin-only (manage_options) by default since v0.3.0. Pass permissionCallback if you need a different policy.

Common mistakes

  • Forgetting ->register() on router/resource
  • Defining restNamespace without vendor/version format (vendor/v1)
  • Assuming middleware auth replaces permission_callback (it does not)

Validation checklist

  • endpoint responds under /wp-json/...
  • error payload includes requestId
  • unknown query params fail with 400 on resource list routes