2026年5月20日

2026年5月20日

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

はじめに

WordPress REST APIにカスタムエンドポイントを追加することで、フロントエンドのJavaScriptや外部アプリからWordPressのデータを取得・操作できます。register_rest_route を使って安全なエンドポイントを実装します。

症状・原因

  • JavaScriptからWordPressのデータをAjaxで取得したい
  • 外部アプリとWordPressをAPI連携したい
  • 既存のREST APIエンドポイントにカスタムフィールドを追加したい
  • REST APIのレスポンスを制御したい

解決手順

ステップ1:基本的なエンドポイントを登録する

// functions.php
function mytheme_register_rest_routes(): void {
    // 名前空間: {vendor}/{version}
    $namespace = 'mytheme/v1';

    // GETエンドポイント: /wp-json/mytheme/v1/posts
    register_rest_route($namespace, '/posts', [
        'methods'             => WP_REST_Server::READABLE, // GET
        'callback'            => 'mytheme_rest_get_posts',
        'permission_callback' => '__return_true', // 認証不要(公開)
        'args'                => [
            'per_page' => [
                'default'           => 10,
                'sanitize_callback' => 'absint',
                'validate_callback' => function ($value): bool {
                    return $value > 0 && $value <= 100;
                },
            ],
            'category' => [
                'default'           => 0,
                'sanitize_callback' => 'absint',
            ],
        ],
    ]);

    // POSTエンドポイント: /wp-json/mytheme/v1/contact(認証必要)
    register_rest_route($namespace, '/contact', [
        'methods'             => WP_REST_Server::CREATABLE, // POST
        'callback'            => 'mytheme_rest_send_contact',
        'permission_callback' => 'mytheme_rest_permission_check',
    ]);
}
add_action('rest_api_init', 'mytheme_register_rest_routes');

ステップ2:コールバック関数を実装する

// functions.php
function mytheme_rest_get_posts(WP_REST_Request $request): WP_REST_Response {
    $per_page = $request->get_param('per_page');
    $category = $request->get_param('category');

    $args = [
        'post_type'      => 'post',
        'post_status'    => 'publish',
        'posts_per_page' => $per_page,
        'no_found_rows'  => false, // ページネーション情報が必要
    ];

    if ($category > 0) {
        $args['cat'] = $category;
    }

    $query = new WP_Query($args);
    $posts = [];

    if ($query->have_posts()) {
        while ($query->have_posts()) {
            $query->the_post();
            $posts[] = [
                'id'        => get_the_ID(),
                'title'     => get_the_title(),
                'excerpt'   => wp_trim_words(get_the_excerpt(), 30),
                'url'       => get_permalink(),
                'date'      => get_the_date('Y-m-d'),
                'thumbnail' => get_the_post_thumbnail_url(null, 'medium') ?: null,
            ];
        }
        wp_reset_postdata();
    }

    $response = new WP_REST_Response([
        'posts'      => $posts,
        'total'      => $query->found_posts,
        'totalPages' => $query->max_num_pages,
    ], 200);

    // キャッシュヘッダーを設定
    $response->header('Cache-Control', 'public, max-age=300');

    return $response;
}

ステップ3:認証とパーミッションを設定する

// functions.php
function mytheme_rest_permission_check(WP_REST_Request $request): bool|WP_Error {
    // ログイン必須
    if (!is_user_logged_in()) {
        return new WP_Error(
            'rest_forbidden',
            'ログインが必要です。',
            ['status' => 401]
        );
    }

    // 特定の権限チェック
    if (!current_user_can('edit_posts')) {
        return new WP_Error(
            'rest_forbidden',
            'この操作を行う権限がありません。',
            ['status' => 403]
        );
    }

    // nonceによるCSRF検証(wp-json経由のJSリクエスト)
    $nonce = $request->get_header('X-WP-Nonce');
    if (!wp_verify_nonce($nonce, 'wp_rest')) {
        return new WP_Error(
            'rest_nonce_invalid',
            'セキュリティトークンが無効です。',
            ['status' => 403]
        );
    }

    return true;
}

ステップ4:JavaScriptから呼び出す

// フロントエンドJS: 認証なし(公開エンドポイント)
async function fetchPosts(perPage = 10) {
    const response = await fetch(
        `/wp-json/mytheme/v1/posts?per_page=${perPage}`,
        { headers: { 'Content-Type': 'application/json' } }
    );

    if (!response.ok) {
        throw new Error(`HTTP error: ${response.status}`);
    }

    return response.json();
}

// ログイン済みユーザー向け(nonce付き)
async function fetchProtected() {
    const response = await fetch('/wp-json/mytheme/v1/contact', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'X-WP-Nonce': wpApiSettings.nonce, // wp_localize_script で渡す
        },
        body: JSON.stringify({ message: 'テストメッセージ' }),
    });

    return response.json();
}
// functions.php: nonce をJSに渡す
function mytheme_localize_api_settings(): void {
    wp_localize_script('mytheme-main', 'wpApiSettings', [
        'root'  => esc_url_raw(rest_url()),
        'nonce' => wp_create_nonce('wp_rest'),
    ]);
}
add_action('wp_enqueue_scripts', 'mytheme_localize_api_settings');

ステップ5:既存エンドポイントにフィールドを追加する

// functions.php
function mytheme_add_rest_fields(): void {
    // 投稿エンドポイントにサムネイルURLを追加
    register_rest_field('post', 'thumbnail_url', [
        'get_callback' => function (array $post): ?string {
            return get_the_post_thumbnail_url($post['id'], 'medium') ?: null;
        },
        'schema' => [
            'description' => 'サムネイル画像URL',
            'type'        => 'string',
            'context'     => ['view'],
        ],
    ]);
}
add_action('rest_api_init', 'mytheme_add_rest_fields');

注意事項

  • permission_callback'__return_true' にすると誰でもアクセスできます。センシティブなデータを返すエンドポイントには必ず認証を追加してください
  • REST APIのレスポンスはキャッシュプラグインにキャッシュされる場合があります。動的データには Cache-Control: no-cache を設定してください
  • カスタムエンドポイントのURLは /wp-json/{namespace}/{route} 形式です。rest_url('mytheme/v1/posts') で正しいURLを生成できます

まとめ

rest_api_init フックで register_rest_route を呼び、名前空間・メソッド・コールバック・パーミッションを指定します。コールバックは WP_REST_Response を返し、認証は permission_callback で制御します。JSからは fetch + X-WP-Nonce ヘッダーで安全に呼び出せます。

お気軽にご相談ください

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