WordPress REST API 完全ガイド - 認証・カスタムエンドポイント・ヘッドレスCMS活用まで

WordPress REST API 完全ガイド - 認証・カスタムエンドポイント・ヘッドレスCMS活用まで

作成日:
更新日:

WordPress REST API とは

WordPress REST API は、WordPress 4.7(2016年)でコアに統合された JSON ベースの公式 API です。/wp-json/ を起点として、投稿・固定ページ・メディア・ユーザー・カテゴリーなどあらゆるリソースを HTTP 経由で読み書きできます。

これによって WordPress は単なる CMS ではなく、「ヘッドレスCMS」「モバイルアプリのバックエンド」「他システムとの連携 API」 としても活用できる存在になりました。

本記事は WordPress 6.8 系・2026年5月時点 の公式リファレンス(developer.wordpress.org/rest-api/)に基づきます。

REST API の基本情報

項目内容
ルート URLhttps://your-site.com/wp-json/
コア名前空間/wp/v2/
データ形式JSON
HTTPメソッドGET / POST / PUT / PATCH / DELETE
認証Cookie (X-WP-Nonce) / Application Passwords / JWT (プラグイン) / OAuth
プラグイン標準で同梱(WP 4.7+)

ブラウザで https://your-site.com/wp-json/ を開くと、利用可能な名前空間とエンドポイントの一覧が JSON で返ってきます。これが REST API の インデックス(discovery エンドポイント) で、どのリソースがあるかを動的に発見できます。

主要エンドポイント一覧

WordPress コアが提供する /wp/v2/ 名前空間の主なエンドポイントです。

エンドポイント用途
/wp/v2/posts投稿の一覧/作成/更新/削除
/wp/v2/posts/<id>単一投稿
/wp/v2/pages固定ページ
/wp/v2/mediaメディア(画像・動画など)
/wp/v2/usersユーザー
/wp/v2/users/me認証済みユーザーの情報
/wp/v2/categoriesカテゴリー
/wp/v2/tagsタグ
/wp/v2/commentsコメント
/wp/v2/taxonomiesタクソノミー定義
/wp/v2/types投稿タイプ定義
/wp/v2/search横断検索
/wp/v2/settingsサイト設定(要認証)
/wp/v2/menusナビメニュー(WP 5.9+)
/wp/v2/blocks再利用可能ブロック

よくあるクエリパラメータ

# 公開済みの最新投稿を10件
GET /wp-json/wp/v2/posts?per_page=10&orderby=date&order=desc
 
# slug 指定で取得
GET /wp-json/wp/v2/posts?slug=hello-world
 
# 特定カテゴリーで絞り込み
GET /wp-json/wp/v2/posts?categories=5
 
# 必要なフィールドだけ返す
GET /wp-json/wp/v2/posts?_fields=id,title,slug,date
 
# 関連リソースを埋め込む(_embed)
GET /wp-json/wp/v2/posts?_embed

特に _fields_embedオーバーフェッチ抑制 に必須です。

認証方法を整理する

REST API の認証は用途で使い分けます。

認証方法使うシーン特徴
Cookie + X-WP-Nonce同一サイト内(管理画面 JS など)デフォルト・最もシンプル
Application Passwords外部スクリプト・CI・サードパーティWP 5.6+ 標準。HTTPS 必須
JWT (プラグイン)SPA / モバイルプラグイン要、トークン制
OAuth 2.0 (プラグイン)サードパーティ統合委任認可が必要なときだけ

WordPress にログインしているユーザーが、管理画面の JS から API を叩く場合の標準。サーバー側で wp_create_nonce('wp_rest') で発行された nonce をリクエストヘッダ X-WP-Nonce に入れます。

// wp_localize_script() で wpApiSettings.nonce が出力されている前提
fetch(`${wpApiSettings.root}wp/v2/posts/1`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-WP-Nonce': wpApiSettings.nonce,
  },
  body: JSON.stringify({ title: 'Hello Moon' }),
})
  .then((r) => r.json())
  .then(console.log);

サーバー側でカスタム検証する場合:

$valid = wp_verify_nonce( $_SERVER['HTTP_X_WP_NONCE'] ?? '', 'wp_rest' );

2. Application Passwords(推奨:外部からの API 利用)

WordPress 5.6 以降の標準機能 で、ユーザーごとに発行できる 24文字のトークン です(公式ドキュメント)。

発行手順

  1. WP 管理画面 → ユーザー → 自分のプロフィール → 「アプリケーションパスワード」
  2. アプリ名を入力 → 「新しいアプリケーションパスワードを追加」
  3. 表示された 24 文字のパスワードを控える(再表示不可)

curl での利用

curl -X GET https://your-site.com/wp-json/wp/v2/posts \
  -u "your-username:ABCD 1234 EFGH 5678 IJKL 9012"

スペース込みのパスワードをそのまま Basic 認証で送ります(WordPress 側でスペースは無視)。

Node.js から呼び出す例

import { Buffer } from "node:buffer";
 
const user = process.env.WP_USER;
const appPass = process.env.WP_APP_PASSWORD; // "ABCD 1234 ..."
const auth = Buffer.from(`${user}:${appPass}`).toString("base64");
 
const res = await fetch("https://your-site.com/wp-json/wp/v2/posts", {
  method: "POST",
  headers: {
    "Authorization": `Basic ${auth}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    title: "API から投稿",
    content: "<p>Hello from Node.js!</p>",
    status: "publish",
  }),
});
 
console.log(await res.json());

重要な制約

  • HTTPS が必須(localhost を除く)。HTTP では Application Passwords は機能しない
  • wp-login.php からは利用不可(API 専用)
  • ユーザー側でいつでも個別に revoke できる
  • 利用可能か事前確認:wp_is_application_passwords_available()

3. JWT 認証(SPA/モバイル向け)

短命なアクセストークンが必要な SPA/モバイルでは、JWT Authentication for WP REST API などのプラグインで JWT を発行して Authorization: Bearer <token> で送る方式が一般的です。リフレッシュ/ローテーションは自前管理が必要なので、要件次第で Application Passwords とどちらを取るか決めます。

カスタムエンドポイントを作る - register_rest_route

WordPress プラグインや functions.php で、自前のエンドポイントを定義できます。WP 5.5 以降、permission_callback の指定は実質必須 で、未指定だとログ警告が出ます。

最小サンプル:公開エンドポイント

add_action( 'rest_api_init', function () {
    register_rest_route( 'mytheme/v1', '/hello', array(
        'methods'             => WP_REST_Server::READABLE, // 'GET'
        'callback'            => 'mytheme_hello',
        'permission_callback' => '__return_true', // 公開ならこれ
    ) );
} );
 
function mytheme_hello( WP_REST_Request $request ) {
    return rest_ensure_response( array(
        'message' => 'Hello, REST!',
        'time'    => current_time( 'mysql' ),
    ) );
}

呼び出し:

curl https://your-site.com/wp-json/mytheme/v1/hello
# {"message":"Hello, REST!","time":"2026-05-07 19:30:00"}

認可付き:管理者のみアクセス可

add_action( 'rest_api_init', function () {
    register_rest_route( 'mytheme/v1', '/private', array(
        'methods'  => WP_REST_Server::READABLE,
        'callback' => 'mytheme_private_data',
        'permission_callback' => function () {
            if ( ! current_user_can( 'manage_options' ) ) {
                return new WP_Error(
                    'rest_forbidden',
                    __( 'この情報を見る権限がありません', 'mytheme' ),
                    array( 'status' => 401 )
                );
            }
            return true;
        },
    ) );
} );
 
function mytheme_private_data() {
    return rest_ensure_response( array( 'secret' => 'classified' ) );
}

URL パラメータ + 検証 + サニタイズ

add_action( 'rest_api_init', function () {
    register_rest_route( 'mytheme/v1', '/author/(?P<id>\d+)', array(
        'methods'  => 'GET',
        'callback' => 'mytheme_get_author',
        'args'     => array(
            'id' => array(
                'description'       => '投稿者の ID',
                'type'              => 'integer',
                'required'          => true,
                'validate_callback' => fn( $v ) => is_numeric( $v ) && (int) $v > 0,
                'sanitize_callback' => 'absint',
            ),
        ),
        'permission_callback' => '__return_true',
    ) );
} );
 
function mytheme_get_author( WP_REST_Request $request ) {
    $id   = (int) $request['id'];
    $user = get_userdata( $id );
    if ( ! $user ) {
        return new WP_Error( 'not_found', '投稿者が見つかりません', array( 'status' => 404 ) );
    }
    return rest_ensure_response( array(
        'id'           => $user->ID,
        'display_name' => $user->display_name,
        'posts_count'  => count_user_posts( $user->ID, 'post' ),
    ) );
}

POST で書き込み + JSON ボディ

add_action( 'rest_api_init', function () {
    register_rest_route( 'mytheme/v1', '/feedback', array(
        'methods'             => WP_REST_Server::CREATABLE, // 'POST'
        'callback'            => 'mytheme_feedback',
        'permission_callback' => function ( WP_REST_Request $request ) {
            return is_user_logged_in();
        },
        'args' => array(
            'rating'  => array(
                'type'              => 'integer',
                'required'          => true,
                'validate_callback' => fn( $v ) => is_numeric( $v ) && $v >= 1 && $v <= 5,
                'sanitize_callback' => 'absint',
            ),
            'comment' => array(
                'type'              => 'string',
                'required'          => false,
                'sanitize_callback' => 'sanitize_textarea_field',
            ),
        ),
    ) );
} );
 
function mytheme_feedback( WP_REST_Request $request ) {
    $user_id = get_current_user_id();
    $rating  = (int) $request['rating'];
    $comment = (string) ( $request['comment'] ?? '' );
 
    add_user_meta( $user_id, 'mytheme_feedback', compact( 'rating', 'comment' ) );
 
    return rest_ensure_response( array( 'ok' => true ) );
}

ポイント:

  • validate_callback は値の妥当性を検証(不正なら 400)
  • sanitize_callback は値を整形(XSS/インジェクション対策)
  • permission_callback は実行可否を判定(不可なら 401/403)

この 3点セット がカスタムエンドポイント設計の基本です。

ヘッドレスCMSとして使う - Next.js との組み合わせ

REST API があれば、WordPress を 「コンテンツ管理だけ」 に使い、表示は Next.js などモダンフロントエンドで行う ヘッドレス構成 が組めます。

Next.js 16 での投稿一覧取得

type WPPost = {
  id: number
  slug: string
  title: { rendered: string }
  excerpt: { rendered: string }
  date: string
}
 
async function getPosts(): Promise<WPPost[]> {
  const res = await fetch(
    "https://wp.example.com/wp-json/wp/v2/posts?per_page=10&_fields=id,slug,title,excerpt,date",
    { next: { revalidate: 600, tags: ["wp-posts"] } } // 10分キャッシュ + タグ再検証
  )
  if (!res.ok) throw new Error("fetch failed")
  return res.json()
}
 
export default async function BlogIndex() {
  const posts = await getPosts()
  return (
    <ul>
      {posts.map((p) => (
        <li key={p.id}>
          <a href={`/blog/${p.slug}`} dangerouslySetInnerHTML={{ __html: p.title.rendered }} />
        </li>
      ))}
    </ul>
  )
}

REST API vs WPGraphQL(プラグイン)

ヘッドレス用途では WPGraphQL プラグインを入れて GraphQL で叩く選択肢もあります。

観点REST APIWPGraphQL
導入標準同梱プラグインが必要
学習コスト低(HTTP/JSON)中(GraphQLの理解)
データ取得エンドポイントごとに固定クエリで必要な分だけ
関連取得_embed か複数リクエスト1リクエストで深く取れる
キャッシュHTTP/CDNと相性良いカスタム工夫が必要
エコシステム巨大Apollo/urql 等で快適

シンプルなブログ/ニュースサイトは REST、商品×レビュー×在庫など関連が深いサイトは WPGraphQL」が大まかな選定基準です。

キャッシュ戦略

WordPress 本体側で重い処理を毎リクエストやるとしんどいので、Transients APIObject Cache(Redis) との併用がほぼ必須です。詳しくは WordPress Transients API 完全ガイド を参照してください。

Next.js 側でも revalidate / revalidateTag / revalidatePath を使って ISR キャッシュを併用するのが定石です。

落とし穴とセキュリティ対策

REST API を有効にしただけで、いくつかの 意図せぬ情報露出 が発生し得ます。

ワナ 1:ユーザー列挙(/wp/v2/users

匿名でも /wp-json/wp/v2/users を叩くと、投稿者の slug(ログイン名と同じケースが多い) が一覧で返ります。これはブルートフォース攻撃の前段になります。

対策:未認証ユーザーには users を見せない

add_filter( 'rest_endpoints', function ( $endpoints ) {
    if ( is_user_logged_in() ) {
        return $endpoints;
    }
    foreach ( array( '/wp/v2/users', '/wp/v2/users/(?P<id>[\d]+)' ) as $route ) {
        if ( isset( $endpoints[ $route ] ) ) {
            unset( $endpoints[ $route ] );
        }
    }
    return $endpoints;
} );

セキュリティ系プラグイン(Wordfence、SiteGuard など)でも同等の遮断ができます。

ワナ 2:CSRF(クロスサイトリクエストフォージェリ)

ログイン中ユーザーのブラウザから攻撃サイト経由で API を叩かれる。

対策:同一サイトの JS で API を叩く場合は必ず X-WP-Nonce を送信する。サードパーティアプリには Application Passwords を使い、Cookie 認証は避ける。

ワナ 3:レート制限なし

WordPress コアにはレート制限がありません。総当たり攻撃や Bot による暴走に弱いです。

対策

  • リバースプロキシ(Apache/Nginx)で IP 単位の mod_evasive / limit_req
  • CDN/WAF(Cloudflare の Rate Limiting Rules)
  • プラグイン(Limit Login Attempts Reloaded など)

ワナ 4:REST API を完全無効化したい場合

「サイト内でも REST API は使わないから止めたい」要件はあり得ますが、ブロックエディタや一部コア機能が REST に依存している ため完全 OFF はおすすめしません。

代替として、特定エンドポイントのみ匿名拒否 が現実解です。

add_filter( 'rest_authentication_errors', function ( $result ) {
    if ( ! empty( $result ) ) {
        return $result; // 既にエラー判定済みならそれを尊重
    }
    if ( ! is_user_logged_in() ) {
        return new WP_Error(
            'rest_not_logged_in',
            __( 'REST API は認証が必要です', 'mytheme' ),
            array( 'status' => 401 )
        );
    }
    return $result;
} );

ワナ 5:raw フィールドで HTML が裸で返る

/wp/v2/posts/<id>?context=edit を編集権限ユーザーで叩くと、title.raw / content.raw未エスケープで返ります。フロントで描画する場合はサニタイズ必須です。

ワナ 6:HTTPSじゃないと致命的

Application Passwords は HTTPS 必須ですが、Cookie 認証も含めて 平文HTTPで API を運用するのは論外 です。サイト全体を HSTS + HTTPS で固めましょう。

セキュリティ・運用チェックリスト

WordPress REST API を本番投入する前のチェックリスト:

  • HTTPS 化(HSTS 含む)
  • Application Passwords を使う場合のみ有効化、不要なら無効化フィルタを設定
  • /wp/v2/users の匿名アクセスを遮断
  • カスタムエンドポイントは 必ず permission_callback を実装
  • argsvalidate_callback + sanitize_callback を両方指定
  • CDN/WAF でレート制限
  • エラー時は WP_Errorstatus 付きで返す(500 を出さない)
  • 重い処理は Transients API + Redis Object Cache でキャッシュ
  • フロント側は _fields / _embed でオーバーフェッチを抑制
  • アクセスログ/監査ログを残し、定期レビュー

まとめ

WordPress REST API は、

  • WP 4.7 以降コア標準/wp-json/wp/v2/ から全リソースを操作できる
  • 認証は 同一サイトJSなら Cookie + X-WP-Nonce、外部から叩くなら Application Passwords が定石
  • register_rest_route + permission_callback + args(validate/sanitize) がカスタム実装の3点セット
  • Next.js などと組み合わせてヘッドレスCMS として使えば、WordPressの編集体験 + モダンフロントの速度を両立できる
  • 一方で ユーザー列挙・CSRF・レート制限なし などの構造的弱点があり、必ず対策しないと事故る

ヘッドレス WordPress や、外部システム連携、スマホアプリのバックエンドなど、用途は今後も広がり続けます。本記事のチェックリストを横に置きながら、安全で軽量な REST API 活用を進めてください。

参考リンク

関連記事