2026年9月20日
2026年9月20日
WordPressのREST APIを拡張してカスタムエンドポイントを作成する方法
WordPressのREST APIに独自のエンドポイントを追加するには、rest_api_init アクションで register_rest_route() を呼び、「誰が呼べるか(permission_callback)」「どんな入力を受け付けるか(args)」「何を返すか(callback)」の3つを定義します。この記事では、読み取り専用の公開エンドポイントと、ログインユーザーだけが使える更新用エンドポイントを作り、既存の投稿レスポンスに項目を足す方法、ブラウザからの呼び出し方、動作確認までを説明します。
コードをどこに書くか
REST APIのエンドポイントは、テーマの見た目ではなくサイトの機能です。テーマの functions.php に書くと、テーマを変更したときにAPIも消えてしまい、そのAPIを使うアプリや外部サービスが動かなくなります。独自プラグイン、または wp-content/mu-plugins/ に置くマストユースプラグインとして書くのが適しています。
作業前にバックアップを取り、ステージング環境で試してから本番に反映してください。PHPの文法エラーがあるとサイト全体が表示されなくなるため、FTPやSSHでファイルを外せる状態を確保しておくと、問題が起きたときに戻せます。
ステップ1:読み取り専用のエンドポイントを作る
まず、コメントの多い公開記事を指定件数だけ返すエンドポイントを作ります。URLは /wp-json/mysite/v1/popular になります。
<?php
/**
* Plugin Name: My Site API
* Description: サイト独自の REST API エンドポイント
*/
add_action( 'rest_api_init', function () {
register_rest_route( 'mysite/v1', '/popular', array(
'methods' => WP_REST_Server::READABLE, // GET
'callback' => 'mysite_get_popular',
'permission_callback' => '__return_true', // 公開データなので誰でも可
'args' => array(
'count' => array(
'type' => 'integer',
'default' => 5,
'minimum' => 1,
'maximum' => 20,
'validate_callback' => 'rest_validate_request_arg',
'sanitize_callback' => 'absint',
),
),
) );
} );
function mysite_get_popular( WP_REST_Request $request ) {
$query = new WP_Query( array(
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => $request->get_param( 'count' ),
'orderby' => 'comment_count',
'order' => 'DESC',
'ignore_sticky_posts' => true,
'no_found_rows' => true,
) );
$items = array();
foreach ( $query->posts as $post ) {
$items[] = array(
'id' => $post->ID,
'title' => get_the_title( $post ),
'link' => get_permalink( $post ),
'comments' => (int) $post->comment_count,
);
}
return rest_ensure_response( $items );
}
ポイントは次のとおりです。
- 名前空間(
mysite/v1):ほかのプラグインと衝突しない固有の名前と、バージョン番号を付けます。後で仕様を変えるときはv2を作れば、既存の利用者を壊さずに済みます - permission_callback:WordPress 5.5 以降、指定しないと開発者向けの通知が出ます。公開データでも
__return_trueと明示します - args:型・最小値・最大値を宣言し、
rest_validate_request_argで検証、absintで整数に整えます。範囲外の値は400エラーとして返ります - 返すデータ:公開してよい項目だけを選んで返します。投稿オブジェクトをそのまま返すと、不要な情報まで外に出ます
ステップ2:認証が必要な更新用エンドポイントを作る
次に、投稿に短いメモをメタデータとして保存するエンドポイントです。URLの一部(投稿ID)を受け取り、その投稿を編集できるユーザーだけが実行できるようにします。先ほどと同じ rest_api_init の中に追加します。
<?php
add_action( 'rest_api_init', function () {
register_rest_route( 'mysite/v1', '/posts/(?P<id>\d+)/note', array(
'methods' => WP_REST_Server::CREATABLE, // POST
'callback' => 'mysite_save_note',
'permission_callback' => function ( WP_REST_Request $request ) {
return current_user_can( 'edit_post', (int) $request['id'] );
},
'args' => array(
'note' => array(
'type' => 'string',
'required' => true,
'validate_callback' => 'rest_validate_request_arg',
'sanitize_callback' => 'sanitize_textarea_field',
),
),
) );
} );
function mysite_save_note( WP_REST_Request $request ) {
$post_id = (int) $request['id'];
if ( ! get_post( $post_id ) ) {
return new WP_Error( 'mysite_not_found', '投稿が見つかりません。', array( 'status' => 404 ) );
}
update_post_meta( $post_id, '_mysite_note', $request->get_param( 'note' ) );
return new WP_REST_Response( array( 'id' => $post_id, 'saved' => true ), 200 );
}
ルートの (?P<id>\d+) は正規表現で、数字だけを id として受け取ります。権限は「ログインしているか」ではなく、current_user_can( 'edit_post', $id ) でその投稿を編集できるかを確認しています。失敗時は WP_Error に HTTPステータスを付けて返すと、呼び出し側でエラーの種類を判別できます。メタキーを _ で始めると、カスタムフィールドの画面に表示されない保護されたメタとして扱われます。
ステップ3:ブラウザから呼び出す(nonceによる認証)
ログイン中のユーザーがサイト上のJavaScriptからAPIを呼ぶ場合、Cookieだけでは認証されず、wp_rest アクションのnonceを X-WP-Nonce ヘッダーで送る必要があります。これはCSRF(他サイトからの不正なリクエスト)を防ぐための仕組みです。
<?php
add_action( 'wp_enqueue_scripts', function () {
if ( ! is_user_logged_in() ) {
return;
}
wp_enqueue_script( 'mysite-note', plugins_url( 'note.js', __FILE__ ), array(), '1.0.0', array( 'in_footer' => true ) );
wp_add_inline_script( 'mysite-note', 'const mysiteApi = ' . wp_json_encode( array(
'root' => esc_url_raw( rest_url( 'mysite/v1/' ) ),
'nonce' => wp_create_nonce( 'wp_rest' ),
) ) . ';', 'before' );
} );
// note.js
async function saveNote(postId, note) {
const res = await fetch(`${mysiteApi.root}posts/${postId}/note`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': mysiteApi.nonce },
body: JSON.stringify({ note }),
});
if (!res.ok) {
const err = await res.json();
throw new Error(err.message);
}
return res.json();
}
外部のサーバーやアプリから呼ぶ場合は、WordPress 5.6 で追加されたアプリケーションパスワードを使い、HTTPSのBasic認証で送るのが標準的な方法です。ログイン用のパスワードをそのままスクリプトに書くのは避けてください。
ステップ4:既存のレスポンスに項目を追加する
新しいエンドポイントを作らなくても、/wp/v2/posts の結果に項目を足すだけで済む場合は register_rest_field() を使います。
<?php
add_action( 'rest_api_init', function () {
register_rest_field( 'post', 'reading_minutes', array(
'get_callback' => function ( $post_arr ) {
$text = wp_strip_all_tags( get_post_field( 'post_content', $post_arr['id'] ) );
return max( 1, (int) ceil( mb_strlen( $text ) / 500 ) ); // 1分500字で概算
},
'schema' => array( 'type' => 'integer', 'context' => array( 'view', 'edit' ) ),
) );
} );
カスタムフィールドをそのまま公開したいだけなら、register_post_meta() で 'show_in_rest' => true を指定する方法もあります。どちらの場合も、非公開にすべき情報が含まれていないかを確認してから公開します。
ステップ5:動作を確認する
curlで確認するのが手軽です。
# 公開エンドポイント
curl -s "https://example.com/wp-json/mysite/v1/popular?count=3"
# 範囲外の値で400エラーになるかを確認
curl -s "https://example.com/wp-json/mysite/v1/popular?count=100"
# 登録されたルートの一覧(名前空間ごと)
curl -s "https://example.com/wp-json/mysite/v1"
パーマリンク設定が「基本」のサイトでは /wp-json/ が使えないため、/?rest_route=/mysite/v1/popular の形式で呼び出します。404の rest_no_route が返る場合はルート名やHTTPメソッドの違い、401や403の場合は permission_callback やnonceを確認します。セキュリティプラグインやWAFがREST APIへのアクセスを制限していることもあります。
運用で気をつけること
公開エンドポイントは誰でも何度でも呼べるため、重いクエリを実行するものは結果をトランジェント(set_transient())などでキャッシュし、必要に応じてアクセス回数の制限を検討します。また、REST APIの仕様や推奨事項は WordPress のバージョンで追加・変更されることがあるため(2026年10月現在)、公式の REST API Handbook で最新の説明を確認してください。
まとめ
- エンドポイントは
rest_api_initでregister_rest_route()を呼んで登録し、テーマではなくプラグインかmu-pluginに書く - permission_callback は必須と考え、公開なら
__return_true、更新系はcurrent_user_can()で対象ごとに権限を確認する - args で型と範囲を宣言して検証・サニタイズし、返すデータは公開してよい項目だけにする
- ブラウザからは
wp_restのnonceをX-WP-Nonceで送り、外部からはアプリケーションパスワードを使う - 既存レスポンスへの追加は
register_rest_field()やregister_post_meta()で済ませられる