2026年8月10日

2026年8月10日

WordPress REST APIにカスタムエンドポイントを追加する方法

はじめに

WordPress REST APIに独自のエンドポイントを追加したい・カスタム投稿タイプをAPI経由で取得できるようにしたい・register_rest_route()の引数の書き方がわからないといった問題の解決方法を解説します。

症状・原因

  • register_rest_route()initフックに登録しているためrest_api_initで正しく動作しない
  • パーミッションコールバックを省略しているため誰でもデータを変更できてしまう
  • カスタム投稿タイプでshow_in_resttrueに設定していないためREST APIに公開されない
  • レスポンスのJSONエンコードで日本語が文字化けする(JSON_UNESCAPED_UNICODE未設定)

解決手順

ステップ1:基本的なカスタムエンドポイントを作成する

// ✅ functions.php または mu-plugins に追加
// 正しいフック:rest_api_init(initではない)
add_action('rest_api_init', function() {

    // ✅ 基本的なGETエンドポイント
    register_rest_route('myapi/v1', '/hello', [
        'methods'             => WP_REST_Server::READABLE, // GET
        'callback'            => function(WP_REST_Request $request) {
            return new WP_REST_Response([
                'message' => 'Hello from WordPress REST API',
                'time'    => current_time('mysql'),
            ], 200);
        },
        'permission_callback' => '__return_true', // 認証不要
    ]);

    // ✅ パラメータ付きエンドポイント(URLパラメータ)
    register_rest_route('myapi/v1', '/post/(?P<id>\d+)', [
        'methods'             => WP_REST_Server::READABLE,
        'callback'            => 'my_get_post_callback',
        'permission_callback' => '__return_true',
        'args'                => [
            'id' => [
                'validate_callback' => function($param) {
                    return is_numeric($param) && (int)$param > 0;
                },
                'sanitize_callback' => 'absint',
                'required'          => true,
                'description'       => '投稿ID',
            ],
        ],
    ]);
});

function my_get_post_callback(WP_REST_Request $request) {
    $post_id = $request->get_param('id');
    $post = get_post($post_id);
    if (!$post) {
        return new WP_Error('post_not_found', '投稿が見つかりません', ['status' => 404]);
    }
    return new WP_REST_Response([
        'id'      => $post->ID,
        'title'   => $post->post_title,
        'content' => apply_filters('the_content', $post->post_content),
        'date'    => $post->post_date,
    ], 200);
}

ステップ2:POST・PUT・DELETEエンドポイントを作成する

// ✅ CRUD対応のエンドポイント
add_action('rest_api_init', function() {

    // ✅ POSTエンドポイント(データ作成)
    register_rest_route('myapi/v1', '/items', [
        [
            'methods'             => WP_REST_Server::READABLE, // GET: 一覧
            'callback'            => 'my_get_items',
            'permission_callback' => '__return_true',
        ],
        [
            'methods'             => WP_REST_Server::CREATABLE, // POST: 作成
            'callback'            => 'my_create_item',
            'permission_callback' => function() {
                return current_user_can('edit_posts'); // 編集権限が必要
            },
            'args' => [
                'title' => [
                    'required'          => true,
                    'type'              => 'string',
                    'sanitize_callback' => 'sanitize_text_field',
                ],
                'content' => [
                    'required'          => false,
                    'type'              => 'string',
                    'sanitize_callback' => 'wp_kses_post',
                ],
            ],
        ],
    ]);

    // ✅ 単一リソースのGET・PUT・DELETE
    register_rest_route('myapi/v1', '/items/(?P<id>\d+)', [
        [
            'methods'             => WP_REST_Server::READABLE,
            'callback'            => 'my_get_item',
            'permission_callback' => '__return_true',
        ],
        [
            'methods'             => WP_REST_Server::EDITABLE, // PUT/PATCH
            'callback'            => 'my_update_item',
            'permission_callback' => function() {
                return current_user_can('edit_posts');
            },
        ],
        [
            'methods'             => WP_REST_Server::DELETABLE, // DELETE
            'callback'            => 'my_delete_item',
            'permission_callback' => function() {
                return current_user_can('delete_posts');
            },
        ],
    ]);
});

function my_create_item(WP_REST_Request $request) {
    $post_id = wp_insert_post([
        'post_title'   => $request->get_param('title'),
        'post_content' => $request->get_param('content') ?? '',
        'post_status'  => 'publish',
        'post_type'    => 'post',
    ]);
    if (is_wp_error($post_id)) {
        return $post_id;
    }
    return new WP_REST_Response(['id' => $post_id, 'message' => '作成しました'], 201);
}

ステップ3:カスタム投稿タイプをREST APIに公開する

# ✅ カスタム投稿タイプのREST API公開状態を確認
curl -s https://example.com/wp-json/wp/v2/ | \
    python3 -c "import json,sys; d=json.load(sys.stdin); print(list(d.get('routes',{}).keys()))" | \
    tr ',' '\n' | grep -v "wp/v2/posts\|wp/v2/pages"
// ✅ カスタム投稿タイプを登録(show_in_rest: true が重要)
register_post_type('product_review', [
    'labels'       => ['name' => '商品レビュー'],
    'public'       => true,
    'show_in_rest' => true,            // REST API に公開
    'rest_base'    => 'product-reviews', // /wp-json/wp/v2/product-reviews
    'rest_controller_class' => 'WP_REST_Posts_Controller',
    'supports'     => ['title', 'editor', 'custom-fields'],
]);

// ✅ 既存の投稿タイプをREST APIに追加(後から追加する場合)
add_filter('register_post_type_args', function($args, $post_type) {
    if ($post_type === 'product_review') {
        $args['show_in_rest'] = true;
    }
    return $args;
}, 10, 2);

// ✅ カスタムフィールドをREST APIレスポンスに追加
add_action('rest_api_init', function() {
    register_rest_field('product_review', 'rating', [
        'get_callback' => function($post) {
            return (int) get_post_meta($post['id'], '_rating', true);
        },
        'update_callback' => function($value, $post) {
            update_post_meta($post->ID, '_rating', absint($value));
        },
        'schema' => ['type' => 'integer', 'description' => '評価(1-5)'],
    ]);
});

ステップ4:エンドポイントをテスト・デバッグする

# ✅ カスタムエンドポイントを確認
curl -s https://example.com/wp-json/myapi/v1/hello | python3 -m json.tool
# → {"message": "Hello from WordPress REST API", "time": "2024-01-01 12:00:00"}

# ✅ パラメータ付きエンドポイントをテスト
curl -s https://example.com/wp-json/myapi/v1/post/1 | python3 -m json.tool
# → {"id": 1, "title": "Hello World", ...}

# ✅ POSTリクエストのテスト(Application Password使用)
APP_PASS="XXXX XXXX XXXX XXXX XXXX XXXX"
curl -s -X POST https://example.com/wp-json/myapi/v1/items \
    -u "admin:${APP_PASS}" \
    -H "Content-Type: application/json" \
    -d '{"title":"テスト投稿","content":"本文です"}' | python3 -m json.tool
# → {"id": 456, "message": "作成しました"}

# ✅ wp-config.php でREST APIのデバッグ
wp eval "
define('SAVEQUERIES', true);
\$request = new WP_REST_Request('GET', '/myapi/v1/hello');
\$response = rest_get_server()->dispatch(\$request);
echo json_encode(\$response->get_data(), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
" --path=/var/www/html/

# ✅ REST API のルートを確認
wp eval "
\$routes = rest_get_server()->get_routes();
\$custom = array_filter(array_keys(\$routes), fn(\$r) => str_starts_with(\$r, '/myapi'));
print_r(array_values(\$custom));
" --path=/var/www/html/

ステップ5:レスポンスをキャッシュして高速化する

// ✅ REST API レスポンスをTransientでキャッシュ
add_action('rest_api_init', function() {
    register_rest_route('myapi/v1', '/cached-data', [
        'methods'             => WP_REST_Server::READABLE,
        'callback'            => function(WP_REST_Request $request) {
            $cache_key = 'myapi_cached_data_' . md5(serialize($request->get_params()));
            $cached = get_transient($cache_key);
            if ($cached !== false) {
                return new WP_REST_Response(array_merge($cached, ['cache' => 'HIT']), 200);
            }
            // 重い処理(DBクエリなど)
            $data = [
                'items' => get_posts(['post_type' => 'product', 'numberposts' => 100]),
                'total' => wp_count_posts('product')->publish,
            ];
            set_transient($cache_key, $data, HOUR_IN_SECONDS);
            return new WP_REST_Response(array_merge($data, ['cache' => 'MISS']), 200);
        },
        'permission_callback' => '__return_true',
    ]);
});

// ✅ 日本語を文字化けせずにJSONで返す
add_filter('rest_pre_echo_response', function($result, $server, $request) {
    // WordPress はデフォルトで JSON_UNESCAPED_UNICODE を使わない場合がある
    // カスタムエンドポイントでは WP_REST_Response を使えば自動処理される
    return $result;
}, 10, 3);

注意事項

  • permission_callbackを省略または'__return_true'にするとすべてのユーザーがアクセスできます。データを変更するエンドポイント(POST/PUT/DELETE)では必ずcurrent_user_can()で権限チェックを行ってください。省略するとWordPress 5.5以降では警告ログが出力されます
  • SQLインジェクションを防ぐため、データベースに直接値を渡す場合は$wpdb->prepare()を必ず使用してください。sanitize_callbackではユーザー入力のサニタイズのみ行い、DBへの直接文字列連結は避けてください

まとめ

REST APIカスタムエンドポイントの作成は①rest_api_initフックにregister_rest_route('namespace/v1', '/route', [...])を登録・callbackpermission_callbackを設定・WP_REST_Responseでレスポンスを返す、②methodsにREADABLE/CREATABLE/EDITABLE/DELETABLEを設定・argsでパラメータのバリデーション・サニタイズ、③カスタム投稿タイプにshow_in_rest: truerest_baseを設定・register_rest_field()でカスタムフィールドを追加、④curlとApplication Passwordでエンドポイントをテスト・rest_get_server()->dispatch()でデバッグ、⑤get_transient()/set_transient()でAPIレスポンスをキャッシュして高速化の手順で実装します。

お気軽にご相談ください

お見積りへ お問い合わせへ