2026年6月16日
2026年6月16日
WordPressのカスタムREST APIをOpenAPI(Swagger)で文書化する方法
自作のREST APIエンドポイントは、register_rest_route() の引数定義をきちんと書いておけば、そこからOpenAPI形式の仕様書をかなりの部分まで自動生成できます。この記事では、WordPressがすでに持っているルート情報を使ってOpenAPIのJSONを書き出し、足りない説明や認証情報を補い、lintとSwagger UI/Redocでの表示まで行う手順を解説します。読み終えるころには、コードと仕様書がずれにくい運用の形が作れるはずです。
WordPressのREST APIが最初から持っている情報
OpenAPIを一から手書きする前に、WordPressがどこまで情報を出しているかを確認しておきます。
https://example.com/wp-json/にアクセスすると、登録済みの名前空間とルートの一覧が返ります。名前空間ごとの一覧(例:/wp-json/myapi/v1)では、各ルートのmethodsとendpoints、その中のargs(パラメーター名・型・必須かどうか・説明)が確認できます。- 個別のルートに
OPTIONSリクエストを送ると、register_rest_route()でschemaを渡している場合はレスポンスのJSON Schemaも返ります。
つまり、引数とスキーマをコード側で丁寧に定義していれば、それがそのまま仕様書の材料になります。逆に言うと、args に description も type も書いていないエンドポイントからは、中身の薄い仕様書しか作れません。
一方で、WordPressのルート情報には次のものが含まれません。ここは人が補う部分です。
- 認証方式(Application Passwords、Cookie+nonceなど)の説明
- エラー時のレスポンス形式や、ステータスコードごとの意味
- レスポンスの具体例と、各操作の目的の説明
方針: コードから生成し、説明は別ファイルで上書きする
OpenAPIの作り方には大きく2通りあります。
- 設計先行(手書き): 先に
openapi.yamlを書き、それに合わせて実装する。チームで仕様を合意してから作る場合に向いています。 - コード先行(生成): 実装の
register_rest_route()から仕様を書き出す。既存のエンドポイントを文書化する場合に向いています。
この記事の対象は「すでに動いているカスタムAPIを文書化したい」ケースなので、2を基本にします。ただし生成だけでは説明が乏しくなるため、生成したJSONに、手書きの補足(認証やエラーの説明)を重ねる運用がおすすめです。こうすると、パラメーターの追加や型の変更はコードを直すだけで仕様書に反映され、文章部分は人が管理できます。
PHPのアトリビュートやアノテーションから生成する swagger-php のようなライブラリもありますが、WordPressではすでに args に型情報があるため、二重管理になりがちです。まずはWordPress自身の定義を活かす方法から試すのが手堅いでしょう。
手順1: register_rest_routeを「文書化に耐える」書き方にする
生成の質は、ここで決まります。次の例は、書籍データを返すエンドポイントです。カスタムプラグインか mu-plugin に置きます(テーマの functions.php に置くと、テーマを変えたときにAPIごと消えてしまうためです)。
<?php
/**
* Plugin Name: My API
*/
add_action( 'rest_api_init', function () {
register_rest_route(
'myapi/v1',
'/books/(?P<id>\d+)',
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => 'myapi_get_book',
'permission_callback' => '__return_true',
'args' => array(
'id' => array(
'description' => '書籍のID',
'type' => 'integer',
'required' => true,
'minimum' => 1,
),
'fields' => array(
'description' => '返す項目の種類',
'type' => 'string',
'enum' => array( 'summary', 'full' ),
'default' => 'summary',
),
),
),
'schema' => 'myapi_book_schema',
)
);
} );
function myapi_book_schema() {
return array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'book',
'type' => 'object',
'properties' => array(
'id' => array( 'type' => 'integer', 'description' => '書籍のID', 'readonly' => true ),
'title' => array( 'type' => 'string', 'description' => '書名' ),
'isbn' => array( 'type' => array( 'string', 'null' ), 'description' => 'ISBN(未登録ならnull)' ),
),
);
}
function myapi_get_book( WP_REST_Request $request ) {
$id = (int) $request['id'];
// 実際のデータ取得処理に置き換えてください
return rest_ensure_response( array( 'id' => $id, 'title' => 'サンプル', 'isbn' => null ) );
}
押さえておきたい点は次の3つです。
- **
permission_callbackは省略しない**: WordPress 5.5以降、省略すると開発者向けの警告が出ます。公開APIなら__return_trueを明示し、書き込み系ならcurrent_user_can()で権限を確認します。仕様書にも「どの権限が要るか」を書けるようになります。 - **
typeとdescriptionをすべての引数に書く**:typeを書いておくと、WordPressがその型で検証とサニタイズを行ってくれるため、仕様書のためだけの作業になりません。 - **
schemaを渡す**: レスポンスの形を定義しておくと、OPTIONSで取得でき、生成スクリプトでレスポンス定義にも使えます。
手順2: WP-CLIコマンドでOpenAPIのJSONを書き出す
ルート情報は rest_get_server()->get_routes() で取得できます。次のコードは、指定した名前空間のルートをOpenAPI 3.1の形に変換して出力するWP-CLIコマンドです。mu-plugin(wp-content/mu-plugins/myapi-openapi.php など)に置くと、管理画面からうっかり無効化されることもありません。
<?php
/**
* Plugin Name: My API OpenAPI Export
*/
if ( defined( 'WP_CLI' ) && WP_CLI ) {
WP_CLI::add_command( 'myapi openapi', function () {
$server = rest_get_server(); // ここで rest_api_init が実行され、ルートが登録される
$routes = $server->get_routes( 'myapi/v1' );
$keys = array( 'type', 'description', 'enum', 'default', 'format', 'minimum', 'maximum', 'items', 'properties' );
$paths = array();
foreach ( $routes as $route => $handlers ) {
// /myapi/v1/books/(?P<id>\d+) を /myapi/v1/books/{id} に変換
$path = preg_replace( '#\(\?P<([A-Za-z0-9_]+)>[^)]+\)#', '{$1}', $route );
preg_match_all( '#\{([A-Za-z0-9_]+)\}#', $path, $m );
$path_params = $m[1];
foreach ( $handlers as $handler ) {
foreach ( array_keys( $handler['methods'] ) as $method ) {
$op = array( 'responses' => array( '200' => array( 'description' => '成功' ) ) );
$params = array();
$body = array();
foreach ( (array) $handler['args'] as $name => $arg ) {
$schema = array_intersect_key( $arg, array_flip( $keys ) );
$in = in_array( $name, $path_params, true ) ? 'path' : 'query';
if ( 'query' === $in && in_array( $method, array( 'POST', 'PUT', 'PATCH' ), true ) ) {
$body[ $name ] = $schema;
continue;
}
$params[] = array(
'name' => $name,
'in' => $in,
'required' => 'path' === $in || ! empty( $arg['required'] ),
'description' => isset( $arg['description'] ) ? $arg['description'] : '',
'schema' => $schema,
);
}
if ( $params ) {
$op['parameters'] = $params;
}
if ( $body ) {
$op['requestBody'] = array(
'content' => array(
'application/json' => array(
'schema' => array( 'type' => 'object', 'properties' => $body ),
),
),
);
}
$paths[ $path ][ strtolower( $method ) ] = $op;
}
}
}
$spec = array(
'openapi' => '3.1.0',
'info' => array( 'title' => 'My API', 'version' => '1.0.0' ),
'servers' => array( array( 'url' => untrailingslashit( rest_url() ) ) ),
'paths' => $paths,
);
WP_CLI::line( wp_json_encode( $spec, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) );
} );
}
実行は次のとおりです。
wp myapi openapi > openapi.json
このスクリプトは出発点として最小限にしてあります。実際に使うと、次のような点でつまずきやすいので、必要に応じて手を入れてください。
- 必須の書き方の違い: WordPressは引数ごとに
'required' => trueと書きますが、OpenAPI(JSON Schema)のオブジェクトでは親側のrequired配列で指定します。リクエストボディのプロパティを必須にしたい場合は、変換時に配列へ集める処理が要ります。 - null許容の書き方:
array( 'string', 'null' )のような型の配列は、OpenAPI 3.1ならそのまま使えますが、3.0ではnullable: trueに変換が必要です。出力するバージョンを最初に決めておくと迷いません。 - WordPress独自のキー:
contextやreadonlyはWordPress固有、あるいは綴りが違うキーです(JSON SchemaではreadOnly)。上のコードでは含めないようにしていますが、レスポンス側のスキーマを流用する場合は変換が必要です。 - ルートの正規表現:
(?P<id>\d+)程度なら変換できますが、括弧を入れ子にした複雑なパターンは崩れることがあります。出力を目で確認してください。
レスポンス定義を充実させたい場合は、$server->get_route_options( $route ) でルートのオプションを取得し、そこに含まれる schema コールバックを呼んで responses に入れる、という拡張ができます。
手順3: 認証とエラーの説明を補う
生成したJSONに、人が書く部分を重ねます。別ファイル(例: openapi.overlay.yaml)で管理し、ビルド時にマージするか、最初は生成後のJSONに手で追記するだけでもかまいません。
WordPressのREST APIでよく使う認証は、外部からの呼び出しなら Application Passwords(WordPress 5.6で導入されたBasic認証)です。OpenAPIでは次のように表現できます。
components:
securitySchemes:
wpApplicationPassword:
type: http
scheme: basic
description: ユーザー名と、プロフィール画面で発行したアプリケーションパスワードを使います。
security:
- wpApplicationPassword: []
ログイン中のブラウザから呼ぶ場合はCookie認証になり、X-WP-Nonce ヘッダーに wp_rest のnonceを付ける必要があります。仕様書には「どちらの認証を想定しているか」をはっきり書いておくと、使う側の混乱が減ります。
エラーについては、WordPressのREST APIは WP_Error を code・message・data.status を持つJSONで返します。共通のエラースキーマとして components.schemas に1つ定義し、各操作の 400・401・403・404 から参照すると、仕様書全体が読みやすくなります。
手順4: lintして、Swagger UIやRedocで表示する
書き出したファイルは、まず機械的に検査します。Node.jsが入っていれば、Redocly CLIで次のように確認できます(2026年10月現在)。コマンドやオプションはバージョンで変わることがあるため、公式ドキュメントも参照してください。
npx @redocly/cli lint openapi.json
npx @redocly/cli build-docs openapi.json -o docs/index.html
lint は構文の誤りだけでなく、description が抜けている操作なども指摘してくれます。手順1で説明を書き込んでいれば、指摘はかなり減るはずです。
見た目の確認には、HTMLファイルとして出力できるRedocのほか、試しにリクエストを送れるSwagger UIも便利です。Swagger UIは swagger-ui-dist パッケージを使えば静的ファイルだけで動きます。
仕様書を公開する場所には注意が必要です。
- 社内向けAPIの仕様は公開サイトに置かない: エンドポイントの一覧と引数は、攻撃者にとっても手がかりになります。社内向けなら、Basic認証をかけた別ホストや、リポジトリ内のファイルとして共有します。
- Swagger UIの「Try it out」で本番を叩かない:
serversにステージングのURLを指定しておくか、本番では書き込み系の操作を試せないように運用を決めておきます。
手順5: 仕様書をコードと一緒に管理する
生成スクリプトがあれば、仕様書の更新は「コマンドを1回実行するだけ」になります。さらに、次の運用にしておくとずれが起きにくくなります。
openapi.jsonをプラグインのリポジトリにコミットし、プルリクエストの差分で仕様の変化をレビューする- CI(GitHub Actionsなど)で、ステージング環境やDockerで立ち上げたWordPressに対して
wp myapi openapiを実行し、コミット済みのファイルと差分があれば失敗させる - 破壊的な変更(引数の削除や型の変更)をするときは、名前空間のバージョンを
myapi/v2に上げ、旧バージョンをしばらく残す
よくあるつまずき
- ルートが1件も出てこない: 名前空間の文字列が
register_rest_route()の第1引数と一致しているか確認してください。また、ルートを登録するプラグインがWP-CLI実行時に読み込まれているか(特定の条件でだけ読み込む作りになっていないか)も確認します。 - パーミッションの警告が出る:
permission_callbackを省略しているエンドポイントがあります。生成のついでに見直すよい機会です。 - 仕様書と実際の挙動が違う:
argsに書いていない値を$request->get_param()で読んでいる箇所があると、仕様書には現れません。受け付ける値はすべてargsに定義するのが、検証の面でも安全です。
まとめ
- WordPressのREST APIは、
register_rest_route()のargsとschemaをルート情報として持っており、OpenAPIの材料として使える rest_get_server()->get_routes()を使うWP-CLIコマンドで仕様書を書き出せば、コードと仕様書のずれを減らせる- 必須指定・null許容・独自キーなど、WordPressとOpenAPIの書き方の違いは変換時に吸収する
- 認証方式やエラー形式は自動生成されないので、別ファイルで補って管理する
- 社内向けAPIの仕様書は公開サイトに置かず、Swagger UIから本番に書き込まない運用にする