WordPress REST APIを有効化する方法|functions.php・プラグイン・エラー対処まで網羅

wp-rest-api-activation WordPress
記事内に広告が含まれています。

WordPressをもっと柔軟に活用したいと思ったとき、必ず耳にするのが「REST API」です。記事やカスタム投稿を外部アプリに連携したり、ReactやVue.jsなどのフロントエンドと組み合わせて“ヘッドレスCMS”として利用したりするには欠かせない仕組みです。しかし、いざ使おうとすると「APIが有効化されていない」「エラーが出て使えない」「セキュリティが心配」といった壁に直面する方も多いのではないでしょうか。特にWordPressの環境によってはREST APIが無効化されていたり、プラグインやテーマの影響でエラーが出たりするケースもあり、初心者にとってはつまずきやすいポイントです。

そこで本記事では「WordPress REST APIを有効化して安全に使えるようにするための知識と手順」をわかりやすく解説します。基本的な仕組みから、実際の有効化方法、セキュリティ対策、さらにはカスタマイズや高度な活用方法まで順を追って紹介するので、初めて挑戦する方も安心して進められるはずです。

この記事を読んでわかること

  • WordPress REST APIの基本的な仕組みと役割
  • REST APIが無効化されているケースとその影響
  • REST APIが有効か確認する2つの方法
  • 管理画面・functions.php・プラグインを使った有効化手順
  • REST APIを安全に運用するための認証設定(Basic認証・Application Passwords・JWT認証)
  • API利用時に気をつけるセキュリティリスクと具体的な対策方法
  • カスタム投稿タイプや独自エンドポイントを扱う実装テクニック
  • よくあるエラー(403・401・404・CORS)の原因と解決方法

この記事を読み終える頃には、WordPress REST APIを有効化するだけでなく、実際の開発や外部サービスとの連携に安心して活用できる状態になれるでしょう。

WordPress REST APIとは?基本の仕組みと有効化の前提知識

WordPress REST APIは、WordPressのコンテンツやデータを外部のアプリケーションやサービスから操作できるようにする仕組みです。従来のWordPressは、管理画面から記事を投稿したり、テーマファイルでコンテンツを表示したりする「一体型」の構造でしたが、REST APIを活用することで、WordPressをデータベース(バックエンド)として使いながら、フロントエンド部分を別の技術で構築することが可能になります。

WordPress REST APIの基本構造と役割

REST APIとは何か

REST(REpresentational State Transfer)APIとは、Webアプリケーション同士がデータをやり取りするための標準的な仕組みです。簡単に言うと、「決められたルールに従って、インターネット経由でデータの取得や更新を行う方法」と考えてください。

例えば、あなたのWordPressサイトが「図書館」だとすると、REST APIは「図書館司書」のような役割を果たします。外部のアプリケーションが「○○という本の情報を教えて」「新しい本を登録して」と依頼すると、REST APIがその要求を理解し、適切にデータを提供したり更新したりしてくれるのです。

WP REST API v2 Documentation
Documentation for version 2.0 of the WP REST API.

主要な構成要素

エンドポイント

エンドポイントとは、データにアクセスするためのURL(住所)のことです。WordPressでは以下のような形式でエンドポイントが提供されています。

<https://example.com/wp-json/wp/v2/posts>   (記事一覧を取得)
<https://example.com/wp-json/wp/v2/posts/123> (ID123の記事を取得)
<https://example.com/wp-json/wp/v2/users>   (ユーザー一覧を取得)

リソース

リソースとは、API経由で操作できるデータの種類を指します。WordPressの場合、投稿(posts)、固定ページ(pages)、ユーザー(users)、カテゴリー(categories)などがリソースに該当します。

HTTPメソッド

データの操作方法を指定するのがHTTPメソッドです。主な4つのメソッドは以下の通りです。

  • GET:データの取得(記事を読み取る)
  • POST:新規データの作成(新しい記事を投稿する)
  • PUT:既存データの更新(記事を編集する)
  • DELETE:データの削除(記事を削除する)

実際のリクエスト例:

GET /wp-json/wp/v2/posts      → 記事一覧を取得
POST /wp-json/wp/v2/posts      → 新しい記事を作成
PUT /wp-json/wp/v2/posts/123    → ID123の記事を更新
DELETE /wp-json/wp/v2/posts/123   → ID123の記事を削除`

REST APIでできること一覧(記事取得・投稿・ユーザー管理など)

WordPress REST APIを活用することで、従来の管理画面を使わずに様々な操作が可能になります。具体的にできることを見てみましょう。

コンテンツ管理

  • 記事・固定ページの取得、作成、更新、削除
  • カスタム投稿タイプのデータ操作
  • カテゴリーやタグの管理
  • メディアファイル(画像・動画)のアップロードと管理
  • コメントの取得・投稿・承認・削除

ユーザー管理

  • ユーザー情報の取得(プロフィール情報など)
  • 新規ユーザーの登録
  • ユーザー権限の管理
  • 認証とログイン処理

サイト設定

  • サイトの基本設定情報の取得
  • テーマやプラグインの情報取得
  • ウィジェットやメニューの管理

具体的な活用例

  • ヘッドレスCMS化
    WordPressをデータベースとして使い、フロントエンドをReact、Vue.js、Next.jsなどのモダンなフレームワークで構築することができます。これにより、高速で柔軟なWebサイトを作成できます。
  • SPA(シングルページアプリケーション)のバックエンド
    ページ遷移なしでコンテンツを動的に読み込むSPAサイトを構築する際に、WordPressをデータ供給源として活用できます。
  • モバイルアプリ連携
    iOSやAndroidアプリからWordPressのコンテンツを取得・投稿することで、モバイル向けのコンテンツ管理アプリを開発できます。
  • マルチサイト管理
    複数のWebサイトやサービスで、同一のWordPressデータベースからコンテンツを共有・配信することが可能です。
  • 外部サービス連携
    SNSの自動投稿、メール配信システムとの連携、ECサイトとの商品情報同期など、様々な外部サービスと連携できます。

REST APIが無効化されているケースとその影響

WordPress REST APIは通常、インストール時から有効になっていますが、以下のような原因で意図せず無効化されることがあります。

無効化される主な原因

  • セキュリティプラグインによる制限
    Wordfence Security、All in One WP Security & Firewallなどのセキュリティプラグインが、セキュリティ強化のためにREST APIアクセスを制限している場合があります。
  • テーマやプラグインのコード
    使用しているテーマやプラグインが、functions.phpファイルやプラグインファイル内でremove_action('rest_api_init')などのコードを実行し、REST APIを無効化している可能性があります。
  • サーバー側の設定
    .htaccessファイルやサーバーのWebアプリケーションファイアウォール(WAF)が、/wp-json/へのアクセスをブロックしている場合があります。
  • WordPressの設定
    管理画面の設定や、カスタマイズによってREST APIが意図的に無効化されている場合があります。

無効化による具体的な影響

REST APIが無効化されていると、以下のような問題が発生します。

WordPressの標準機能への影響

  • ブロックエディター(Gutenberg)の一部機能が正常に動作しない
  • サイトヘルスチェック機能が正常に動作しない
  • 一部のプラグインやテーマの機能が制限される

外部連携の不具合

  • モバイルアプリからの投稿・閲覧ができない
  • 外部サービスとの自動連携が停止する
  • ヘッドレスCMS構成のサイトが表示されない

開発・保守への影響

  • APIを使用したカスタム機能が動作しない
  • 外部ツールからのコンテンツ管理ができない
  • 自動バックアップやデータ同期が失敗する

これらの問題を避けるためにも、REST APIの状態を定期的に確認し、適切に有効化しておくことが重要です。次のセクションでは、具体的な有効化手順について詳しく解説していきます。

WordPress REST APIを有効化する具体的な手順

WordPress REST APIが正常に動作しているかを確認し、無効化されている場合の有効化方法について、実践的な手順を解説します。まずは現在のREST APIの状態を確認することから始めましょう。

WordPress REST APIが有効か確認する2つの方法

REST APIの有効化作業を行う前に、現在のAPIが正常に動作しているかを確認することが重要です。以下の2つの方法で簡単に確認できます。

方法1:ブラウザでの確認

最も簡単な確認方法は、ブラウザのアドレスバーに以下のURLを入力することです。

<https://あなたのサイトURL/wp-json/>

例:https://example.com/wp-json/

正常な場合の表示例 REST APIが有効な場合、以下のようなJSON形式のデータが表示されます。

{
  "name": "サイト名",
  "description": "サイトの説明",
  "url": "<https://example.com>",
  "home": "<https://example.com>",
  "routes": {
    "/wp/v2/posts": {
      "namespace": "wp/v2",
      "methods": ["GET", "POST"]
    }
  }
}

問題がある場合の表示例

  • 404エラー:「ページが見つかりません」→ REST APIが完全に無効化されている
  • 403エラー:「アクセスが禁止されています」→ アクセス制限がかかっている
  • 500エラー:「内部サーバーエラー」→ サーバー設定やプラグインの競合

方法2:コマンドラインでの確認

より詳細な情報を取得したい場合は、curlコマンドを使用します。

curl -i <https://あなたのサイトURL/wp-json/wp/v2/posts>

このコマンドを実行すると、HTTPステータスコードとレスポンスヘッダーも含めた詳細な情報が表示されます。

正常な場合のレスポンス例

HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
...

[
  {
    "id": 1,
    "date": "2024-01-01T00:00:00",
    "title": {
      "rendered": "記事タイトル"
    },
    "content": {
      "rendered": "記事内容..."
    }
  }
]

認証が必要な操作の確認

投稿や更新など、認証が必要な操作を確認する場合:

curl -X POST <https://あなたのサイトURL/wp-json/wp/v2/posts> \\
  -H "Content-Type: application/json" \\
  -d '{"title":"テスト投稿","content":"テスト内容","status":"draft"}'

管理画面・functions.phpでREST APIを有効化する方法

REST APIが無効化されている場合、以下の方法で再有効化することができます。

functions.phpを使用した有効化

テーマのfunctions.phpファイルにコードを追加してREST APIを有効化する方法です。

基本的な有効化コード

※一時的な検証用として使うものであり、通常は不要です

<?php
// REST APIを強制的に有効化
add_action('init', function() {
    // REST APIが無効化されている場合に再有効化
    if (!function_exists('rest_get_url_prefix')) {
        add_action('rest_api_init', 'rest_api_loaded');
    }
});

// REST APIのCORS(クロスオリジンリクエスト)を有効化
add_action('rest_api_init', function() {
    remove_filter('rest_pre_serve_request', 'rest_send_cors_headers');
    add_filter('rest_pre_serve_request', function($served, $result, $request, $server) {
        header('Access-Control-Allow-Origin: *');
        header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
        header('Access-Control-Allow-Headers: Content-Type, Authorization');
        return $served;
    }, 10, 4);
});

特定のエンドポイントのみ有効化

セキュリティを考慮して、特定のエンドポイントのみを有効化したい場合:

<?php
// 特定のREST APIエンドポイントのみ有効化
add_filter('rest_authentication_errors', function($result) {
    // 現在のエンドポイントを取得
    global $wp;
    $route = $wp->query_vars['rest_route'] ?? '';

    // 投稿関連のエンドポイントのみ許可
    $allowed_routes = [
        '/wp/v2/posts',
        '/wp/v2/pages',
        '/wp/v2/media'
    ];

    foreach ($allowed_routes as $allowed_route) {
        if (strpos($route, $allowed_route) === 0) {
            return $result; // アクセス許可
        }
    }

    // その他のエンドポイントは無効化
    return new WP_Error('rest_disabled', 'REST API is disabled for this endpoint.', array('status' => 401));
});

WordPress管理画面からの設定

一部のケースでは、WordPressの管理画面から設定を変更することでREST APIの状態を制御できます。

プライバシー設定の確認

  1. WordPress管理画面にログイン
  2. 「設定」→「プライバシー」を選択
  3. 「検索エンジンでの表示」設定を確認
  4. 「検索エンジンがサイトをインデックスしないようにする」にチェックが入っている場合は外す

パーマリンク設定の更新

REST APIのURLが正常に生成されない場合、パーマリンク設定を更新することで解決する場合があります。

  1. 「設定」→「パーマリンク」を選択
  2. 現在の設定を確認
  3. 一度別の設定に変更して「変更を保存」
  4. 元の設定に戻して再度「変更を保存」

プラグイン(WP REST API Controller, ACF to REST API など)の活用方法

手動でのコード変更が不安な場合や、より高度な機能が必要な場合は、専用プラグインを活用することをお勧めします。

WP REST API Controller

REST APIの有効化・無効化を管理画面から簡単に制御できるプラグインです。

インストール手順

  1. WordPress管理画面の「プラグイン」→「新規追加」
  2. 「WP REST API Controller」で検索
  3. インストール・有効化

基本的な使い方

  1. 管理画面に「REST API」メニューが追加される
  2. 「Settings」から全体設定を変更
  3. 「Endpoints」から個別エンドポイントの有効・無効を設定
  4. 「Users」からユーザー別のアクセス権限を設定

主な機能

  • エンドポイント単位でのアクセス制御
  • ユーザーロール別のAPI利用制限
  • IPアドレスによるアクセス制限
  • APIキーによる認証機能

ACF to REST API

Advanced Custom Fields(ACF)のデータをREST API経由で取得したい場合に必須のプラグインです。

ACF to REST API
Exposes Advanced Custom Fields Endpoints in the WordPress REST API

インストール方法

# Composer経由でのインストール(推奨)
composer require airesvsg/acf-to-rest-api

# または WordPress管理画面から「ACF to REST API」を検索してインストール

基本設定

プラグインを有効化すると、ACFフィールドが自動的にREST APIレスポンスに含まれるようになります。

{
  "id": 123,
  "title": {
    "rendered": "記事タイトル"
  },
  "acf": {
    "custom_field_1": "カスタムフィールドの値",
    "image_field": {
      "url": "<https://example.com/image.jpg>",
      "alt": "画像の説明"
    }
  }
}

カスタムフィールドの表示制御

特定のフィールドのみをAPI経由で公開したい場合:

<?php
// functions.phpに追加
add_filter('acf/rest_api/field_settings/show_in_rest', '__return_true');

// 特定のフィールドのみ表示
add_filter('acf/rest_api/field_settings/show_in_rest', function($show_in_rest, $field) {
    // 公開したいフィールド名を指定
    $allowed_fields = ['field_name_1', 'field_name_2'];

    if (in_array($field['name'], $allowed_fields)) {
        return true;
    }

    return false;
}, 10, 2);

その他の推奨プラグイン

JWT Authentication for WP-API
JSON Web Token(JWT)による認証を実装できるプラグインです。

JWT Authentication for WP REST API
Extends the WP REST API using JSON Web Tokens Authentication as an authentication method.

WP GraphQL
REST APIの代替として、GraphQLを使用したい場合のプラグインです。

WPGraphQL
WPGraphQL adds a flexible and powerful GraphQL API to WordPress, enabling efficient querying and interaction with your site's data.

これらの手順を順番に実行することで、WordPress REST APIを確実に有効化できます。次のセクションでは、有効化後のセキュリティ対策について詳しく解説していきます。

誰でも簡単に使える!WordPressテーマ『XWRITE(エックスライト)』

認証とセキュリティ:安全にREST APIを運用する方法

WordPress REST APIを有効化すると、サイトのデータに外部からアクセスできるようになりますが、適切なセキュリティ対策を講じなければ、機密情報の漏洩や不正操作のリスクが高まります。このセクションでは、REST APIに潜む脆弱性と、安全な運用のための具体的な対策方法を詳しく解説します。

知らないと危険!REST APIの脆弱性

WordPress REST APIは便利な機能である一方、セキュリティリスクも抱えています。これらのリスクを理解せずに運用すると、深刻な被害を受ける可能性があります。

主要なセキュリティリスク

情報漏洩のリスク

REST APIはデフォルトで多くの情報を公開します。特に以下のような機密情報が意図せず流出する可能性があります。

  • ユーザー情報(ユーザー名、メールアドレス、プロフィール情報)
  • 下書き記事や非公開記事のメタデータ
  • プラグインやテーマの詳細情報
  • サイトの内部構造やデータベース情報

例えば、以下のURLにアクセスするだけで、サイトのユーザー一覧が取得できてしまいます:

<https://example.com/wp-json/wp/v2/users>

不正な投稿・編集のリスク

認証が適切に設定されていない場合、以下のような不正操作が可能になります。

  • 記事の無断投稿・編集・削除
  • ユーザーアカウントの作成・変更
  • サイト設定の変更
  • メディアファイルの不正アップロード

DDoS攻撃の標的

REST APIエンドポイントは、分散サービス拒否攻撃(DDoS)の標的になりやすく、大量のリクエストによってサーバーがダウンする可能性があります。

SQLインジェクション攻撃

不適切にカスタマイズされたエンドポイントでは、SQLインジェクション攻撃によってデータベースが不正に操作される危険性があります。

実際の攻撃事例

  • ユーザー名の大量収集
    攻撃者が/wp-json/wp/v2/usersエンドポイントを利用して、複数のWordPressサイトからユーザー名を大量収集し、ブルートフォース攻撃に悪用するケースが報告されています。
  • REST API経由の不正投稿
    認証が甘いサイトで、REST API経由でスパム記事を大量投稿される被害が発生しています。

特定権限ユーザーのみAPIアクセスを許可する制御方法とセキュリティ対策

REST APIへのアクセスを適切に制限することで、上記のリスクを大幅に軽減できます。

ユーザーロール別のアクセス制御

特定のユーザーロール(管理者、編集者など)のみにAPIアクセスを許可する設定方法を解説します。

functions.phpでのアクセス制御

<?php
// 管理者と編集者のみREST APIアクセスを許可
add_filter('rest_authentication_errors', function($result) {
    // ユーザーがログインしていない場合
    if (!is_user_logged_in()) {
        return new WP_Error(
            'rest_not_logged_in',
            'API access requires authentication.',
            array('status' => 401)
        );
    }

    // 許可するユーザーロール
    $allowed_roles = ['administrator', 'editor'];
    $user = wp_get_current_user();
    $user_roles = $user->roles;

    // ユーザーロールをチェック
    $has_permission = false;
    foreach ($allowed_roles as $role) {
        if (in_array($role, $user_roles)) {
            $has_permission = true;
            break;
        }
    }

    if (!$has_permission) {
        return new WP_Error(
            'rest_forbidden',
            'Insufficient permissions to access API.',
            array('status' => 403)
        );
    }

    return $result;
});

エンドポイント別の細かい制御

<?php
// エンドポイント別にアクセス権限を設定
add_filter('rest_pre_dispatch', function($result, $server, $request) {
    $route = $request->get_route();
    $method = $request->get_method();

    // ユーザー情報のエンドポイントは管理者のみ
    if (strpos($route, '/wp/v2/users') === 0) {
        if (!current_user_can('manage_options')) {
            return new WP_Error(
                'rest_forbidden',
                'Only administrators can access user data.',
                array('status' => 403)
            );
        }
    }

    // 投稿の作成・更新は編集者以上のみ
    if (strpos($route, '/wp/v2/posts') === 0 && in_array($method, ['POST', 'PUT', 'DELETE'])) {
        if (!current_user_can('edit_posts')) {
            return new WP_Error(
                'rest_forbidden',
                'Insufficient permissions to modify posts.',
                array('status' => 403)
            );
        }
    }

    return $result;
}, 10, 3);

IPアドレス制限

特定のIPアドレスからのアクセスのみを許可する方法です。

<?php
// 特定IPアドレスからのアクセスのみ許可
add_filter('rest_pre_dispatch', function($result, $server, $request) {
    // 許可するIPアドレス
    $allowed_ips = [
        '192.168.1.100',
        '203.0.113.0/24', // CIDR記法も可能
        '::1' // IPv6のlocalhost
    ];

    $client_ip = $_SERVER['REMOTE_ADDR'] ?? '';

    // IPアドレスチェック
    if (!in_array($client_ip, $allowed_ips)) {
        // CIDR記法のチェック
        $ip_allowed = false;
        foreach ($allowed_ips as $allowed_ip) {
            if (strpos($allowed_ip, '/') !== false) {
                if (ip_in_range($client_ip, $allowed_ip)) {
                    $ip_allowed = true;
                    break;
                }
            }
        }

        if (!$ip_allowed) {
            return new WP_Error(
                'rest_ip_forbidden',
                'API access not allowed from this IP address.',
                array('status' => 403)
            );
        }
    }

    return $result;
}, 10, 3);

// IP範囲チェック用の関数
function ip_in_range($ip, $range) {
    list($subnet, $bits) = explode('/', $range);
    $ip_long = ip2long($ip);
    $subnet_long = ip2long($subnet);
    $mask = -1 << (32 - $bits);
    $subnet_long &= $mask;
    return ($ip_long & $mask) == $subnet_long;
}

APIキーによる認証

カスタムAPIキーシステムを実装する方法です。

<?php
// APIキー認証システム
add_filter('rest_pre_dispatch', function($result, $server, $request) {
    // APIキーが必要なエンドポイント
    $protected_endpoints = [
        '/wp/v2/posts',
        '/wp/v2/users',
        '/wp/v2/media'
    ];

    $route = $request->get_route();
    $needs_api_key = false;

    foreach ($protected_endpoints as $endpoint) {
        if (strpos($route, $endpoint) === 0) {
            $needs_api_key = true;
            break;
        }
    }

    if ($needs_api_key) {
        // ヘッダーからAPIキーを取得
        $api_key = $request->get_header('X-API-Key') ??
                  $request->get_param('api_key');

        if (empty($api_key)) {
            return new WP_Error(
                'rest_missing_api_key',
                'API key is required.',
                array('status' => 401)
            );
        }

        // 有効なAPIキーかチェック
        if (!validate_api_key($api_key)) {
            return new WP_Error(
                'rest_invalid_api_key',
                'Invalid API key.',
                array('status' => 401)
            );
        }
    }

    return $result;
}, 10, 3);

// APIキーの検証関数
function validate_api_key($api_key) {
    // データベースまたは設定からAPIキーを取得
    $valid_api_keys = get_option('custom_api_keys', []);

    return in_array($api_key, $valid_api_keys);
}

// APIキー管理機能(管理画面用)
add_action('admin_menu', function() {
    add_options_page(
        'API Keys',
        'API Keys',
        'manage_options',
        'api-keys',
        'api_keys_admin_page'
    );
});

function api_keys_admin_page() {
    if (isset($_POST['generate_key'])) {
        $new_key = wp_generate_uuid4();
        $keys = get_option('custom_api_keys', []);
        $keys[] = $new_key;
        update_option('custom_api_keys', $keys);
        echo '<div class="notice notice-success"><p>New API Key: ' . esc_html($new_key) . '</p></div>';
    }
    ?>
    <div class="wrap">
        <h1>API Keys Management</h1>
        <form method="post">
            <?php wp_nonce_field('generate_api_key'); ?>
            <input type="submit" name="generate_key" class="button-primary" value="Generate New API Key">
        </form>

        <h2>Existing Keys</h2>
        <?php
        $keys = get_option('custom_api_keys', []);
        if (!empty($keys)) {
            echo '<ul>';
            foreach ($keys as $key) {
                echo '<li>' . esc_html($key) . '</li>';
            }
            echo '</ul>';
        } else {
            echo '<p>No API keys generated yet.</p>';
        }
        ?>
    </div>
    <?php
}

Basic認証・Application Passwords・JWT認証の実装手順

より堅牢な認証システムを実装するための3つの主要な方法を解説します。

Basic認証の実装

HTTP Basic認証は最もシンプルな認証方法ですが、HTTPS環境でのみ使用することを強く推奨します。

functions.phpでの実装

<?php
// Basic認証の実装
add_filter('rest_authentication_errors', function($result) {
    // Basic認証ヘッダーをチェック
    if (!isset($_SERVER['HTTP_AUTHORIZATION'])) {
        return new WP_Error(
            'rest_no_auth',
            'Authentication required.',
            array('status' => 401)
        );
    }

    // Basic認証ヘッダーを解析
    $auth_header = $_SERVER['HTTP_AUTHORIZATION'];
    if (strpos($auth_header, 'Basic ') !== 0) {
        return new WP_Error(
            'rest_invalid_auth',
            'Invalid authentication method.',
            array('status' => 401)
        );
    }

    $credentials = base64_decode(substr($auth_header, 6));
    list($username, $password) = explode(':', $credentials, 2);

    // WordPressユーザー認証
    $user = wp_authenticate($username, $password);
    if (is_wp_error($user)) {
        return new WP_Error(
            'rest_authentication_failed',
            'Authentication failed.',
            array('status' => 401)
        );
    }

    // ユーザーを設定
    wp_set_current_user($user->ID);

    return $result;
});

curlでのテスト方法

# Basic認証を使用したAPI呼び出し
curl -X GET \\
  -H "Authorization: Basic $(echo -n 'username:password' | base64)" \\
  <https://example.com/wp-json/wp/v2/posts>

Application Passwordsの活用

WordPress 5.6以降で標準搭載されたApplication Passwordsは、API専用のパスワードを生成できる機能です。

Application Passwordsの有効化

<?php
// Application Passwordsを明示的に有効化
add_filter('wp_is_application_passwords_available', '__return_true');

// Application Passwordsのサポートを追加
add_action('init', function() {
    if (!wp_is_application_passwords_available()) {
        return;
    }

    // カスタムアプリケーション名を登録
    add_filter('wp_application_passwords_get_available_apps', function($apps) {
        $apps['custom-api'] = array(
            'name' => 'Custom API Access',
            'uuid' => wp_generate_uuid4(),
        );
        return $apps;
    });
});

Application Passwordsの生成手順

  1. WordPress管理画面のユーザープロフィールページにアクセス
  2. 「アプリケーションパスワード」セクションを確認
  3. 新しいアプリケーション名を入力
  4. 「新しいアプリケーションパスワードを追加」をクリック
  5. 生成されたパスワードを安全な場所に保存

使用例

# Application Passwordsを使用したAPI呼び出し
curl -X POST \\
  -u username:xxxx-xxxx-xxxx-xxxx \\
  -H "Content-Type: application/json" \\
  -d '{"title":"Test Post","content":"Test content","status":"publish"}' \\
  <https://example.com/wp-json/wp/v2/posts>

JWT認証の実装

JSON Web Token(JWT)は、最も現代的で安全な認証方法の一つです。

JWT Authentication for WP-APIプラグインの使用

# プラグインのインストール
wp plugin install jwt-authentication-for-wp-rest-api --activate

wp-config.phpでの設定

<?php
// JWT用の秘密鍵を設定
define('JWT_AUTH_SECRET_KEY', 'your-secret-key-here');
define('JWT_AUTH_CORS_ENABLE', true);

functions.phpでのカスタム設定

<?php
// JWT認証の設定
add_filter('jwt_auth_whitelist', function($endpoints) {
    // 認証不要のエンドポイントを指定
    return array(
        '/wp-json/jwt-auth/v1/token',
        '/wp-json/jwt-auth/v1/token/validate',
        '/wp-json/wp/v2/posts' // 投稿一覧は認証不要
    );
});

// JWTトークンにカスタムクレームを追加
add_filter('jwt_auth_token_before_sign', function($token, $user) {
    $token['custom_data'] = array(
        'user_role' => $user->roles[0] ?? 'subscriber',
        'permissions' => get_user_meta($user->ID, 'api_permissions', true)
    );
    return $token;
}, 10, 2);

JWTトークンの取得と使用

# トークン取得
curl -X POST \\
  -H "Content-Type: application/json" \\
  -d '{"username":"your_username","password":"your_password"}' \\
  <https://example.com/wp-json/jwt-auth/v1/token>

# レスポンス例
{
  "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
  "user_email": "user@example.com",
  "user_nicename": "username",
  "user_display_name": "Display Name"
}

# トークンを使用したAPI呼び出し
curl -X GET \\
  -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..." \\
  <https://example.com/wp-json/wp/v2/posts>

カスタムJWT実装(高度なユーザー向け)

<?php
// 軽量なJWT実装
class SimpleJWT {
    private $secret_key;

    public function __construct($secret_key) {
        $this->secret_key = $secret_key;
    }

    public function encode($payload) {
        $header = json_encode(['typ' => 'JWT', 'alg' => 'HS256']);
        $payload = json_encode($payload);

        $header_encoded = $this->base64url_encode($header);
        $payload_encoded = $this->base64url_encode($payload);

        $signature = hash_hmac('sha256',
            $header_encoded . '.' . $payload_encoded,
            $this->secret_key, true);
        $signature_encoded = $this->base64url_encode($signature);

        return $header_encoded . '.' . $payload_encoded . '.' . $signature_encoded;
    }

    public function decode($jwt) {
        $parts = explode('.', $jwt);
        if (count($parts) !== 3) {
            return false;
        }

        list($header, $payload, $signature) = $parts;

        // 署名検証
        $expected_signature = hash_hmac('sha256',
            $header . '.' . $payload,
            $this->secret_key, true);

        if (!hash_equals($this->base64url_decode($signature), $expected_signature)) {
            return false;
        }

        return json_decode($this->base64url_decode($payload), true);
    }

    private function base64url_encode($data) {
        return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
    }

    private function base64url_decode($data) {
        return base64_decode(str_pad(strtr($data, '-_', '+/'),
            strlen($data) % 4, '=', STR_PAD_RIGHT));
    }
}

// JWT認証フィルター
add_filter('rest_authentication_errors', function($result) {
    $auth_header = $_SERVER['HTTP_AUTHORIZATION'] ?? '';

    if (strpos($auth_header, 'Bearer ') === 0) {
        $token = substr($auth_header, 7);
        $jwt = new SimpleJWT(JWT_AUTH_SECRET_KEY);
        $payload = $jwt->decode($token);

        if ($payload && isset($payload['user_id'])) {
            // トークン有効期限チェック
            if (isset($payload['exp']) && $payload['exp'] < time()) {
                return new WP_Error('jwt_expired', 'Token has expired.', array('status' => 401));
            }

            wp_set_current_user($payload['user_id']);
            return $result;
        }

        return new WP_Error('jwt_invalid', 'Invalid token.', array('status' => 401));
    }

    return $result;
});

これらの認証方法を適切に実装することで、WordPress REST APIを安全に運用できます。次のセクションでは、さらに高度な実装とカスタマイズ方法について解説していきます。

REST APIを活用した高度な実装とカスタマイズ

WordPress REST APIの基本的な使用方法を理解したら、次は自分のプロジェクトに特化したカスタマイズを行いましょう。このセクションでは、カスタム投稿タイプやカスタムフィールドの活用、独自エンドポイントの作成、APIレスポンスのカスタマイズなど、より実践的で高度な実装方法を詳しく解説します。

カスタム投稿タイプやカスタムフィールドをAPIで扱う方法

WordPressの標準的な投稿や固定ページだけでなく、カスタム投稿タイプやカスタムフィールドもREST API経由で操作できるように設定することで、より柔軟なWebアプリケーションを構築できます。

カスタム投稿タイプのREST API対応

まず、カスタム投稿タイプをREST APIで利用できるようにする基本的な設定方法を解説します。

既存のカスタム投稿タイプをREST API対応にする

<?php
// functions.phpに追加
// 既に登録済みのカスタム投稿タイプにREST APIサポートを追加
add_action('init', function() {
    // 'product'カスタム投稿タイプの例
    global $wp_post_types;

    if (isset($wp_post_types['product'])) {
        $wp_post_types['product']->show_in_rest = true;
        $wp_post_types['product']->rest_base = 'products';
        $wp_post_types['product']->rest_controller_class = 'WP_REST_Posts_Controller';
    }
});

新規カスタム投稿タイプをREST API対応で作成

<?php
// REST API対応のカスタム投稿タイプを新規作成
add_action('init', function() {
    register_post_type('portfolio', array(
        'label' => 'ポートフォリオ',
        'public' => true,
        'has_archive' => true,
        'supports' => array('title', 'editor', 'thumbnail', 'custom-fields'),

        // REST API設定
        'show_in_rest' => true,           // REST APIで表示
        'rest_base' => 'portfolios',      // APIエンドポイントのURL
        'rest_controller_class' => 'WP_REST_Posts_Controller',

        // その他の設定
        'menu_icon' => 'dashicons-portfolio',
        'capability_type' => 'post',
        'hierarchical' => false,

        'labels' => array(
            'name' => 'ポートフォリオ',
            'singular_name' => 'ポートフォリオ',
            'add_new' => '新規追加',
            'add_new_item' => '新しいポートフォリオを追加',
            'edit_item' => 'ポートフォリオを編集',
            'new_item' => '新しいポートフォリオ',
            'view_item' => 'ポートフォリオを表示',
            'search_items' => 'ポートフォリオを検索',
            'not_found' => 'ポートフォリオが見つかりません',
            'not_found_in_trash' => 'ゴミ箱にポートフォリオはありません'
        )
    ));
});

カスタムタクソノミーのREST API対応

カスタム投稿タイプと併せて、カスタムタクソノミーもREST APIで利用できるようにします。

<?php
// REST API対応のカスタムタクソノミーを作成
add_action('init', function() {
    register_taxonomy('portfolio_category', 'portfolio', array(
        'label' => 'ポートフォリオカテゴリー',
        'public' => true,
        'hierarchical' => true,

        // REST API設定
        'show_in_rest' => true,
        'rest_base' => 'portfolio-categories',
        'rest_controller_class' => 'WP_REST_Terms_Controller',

        'labels' => array(
            'name' => 'ポートフォリオカテゴリー',
            'singular_name' => 'ポートフォリオカテゴリー',
            'add_new_item' => '新しいカテゴリーを追加',
            'edit_item' => 'カテゴリーを編集',
        )
    ));
});

カスタムフィールドのREST API対応

標準のカスタムフィールドやAdvanced Custom Fields(ACF)のデータをREST API経由で取得・更新する方法を解説します。

標準のカスタムフィールドをAPIに追加

<?php
// カスタムフィールドをREST APIレスポンスに追加
add_action('rest_api_init', function() {
    // 投稿にカスタムフィールドを追加
    register_rest_field('post', 'custom_price', array(
        'get_callback' => function($post) {
            return get_post_meta($post['id'], 'price', true);
        },
        'update_callback' => function($value, $post) {
            return update_post_meta($post->ID, 'price', sanitize_text_field($value));
        },
        'schema' => array(
            'description' => '商品価格',
            'type' => 'string',
            'context' => array('view', 'edit')
        )
    ));

    // ポートフォリオにスキル情報を追加
    register_rest_field('portfolio', 'skills', array(
        'get_callback' => function($post) {
            $skills = get_post_meta($post['id'], 'skills', true);
            return $skills ? explode(',', $skills) : array();
        },
        'update_callback' => function($value, $post) {
            if (is_array($value)) {
                $skills_string = implode(',', array_map('sanitize_text_field', $value));
                return update_post_meta($post->ID, 'skills', $skills_string);
            }
            return false;
        },
        'schema' => array(
            'description' => 'スキル一覧',
            'type' => 'array',
            'items' => array('type' => 'string'),
            'context' => array('view', 'edit')
        )
    ));
});

ACF(Advanced Custom Fields)との連携

<?php
// ACFフィールドを個別にREST APIに追加
add_action('rest_api_init', function() {
    if (!function_exists('get_field')) {
        return; // ACFが無効な場合は処理しない
    }

    // 画像フィールドの詳細情報を取得
    register_rest_field('portfolio', 'featured_image_details', array(
        'get_callback' => function($post) {
            $image_id = get_field('featured_image', $post['id']);
            if (!$image_id) return null;

            return array(
                'id' => $image_id,
                'url' => wp_get_attachment_image_url($image_id, 'full'),
                'sizes' => array(
                    'thumbnail' => wp_get_attachment_image_url($image_id, 'thumbnail'),
                    'medium' => wp_get_attachment_image_url($image_id, 'medium'),
                    'large' => wp_get_attachment_image_url($image_id, 'large'),
                ),
                'alt' => get_post_meta($image_id, '_wp_attachment_image_alt', true),
                'caption' => wp_get_attachment_caption($image_id)
            );
        },
        'schema' => array(
            'description' => 'Featured image with detailed information',
            'type' => 'object',
            'context' => array('view', 'edit')
        )
    ));

    // リピーターフィールドの処理
    register_rest_field('portfolio', 'project_details', array(
        'get_callback' => function($post) {
            $details = get_field('project_details', $post['id']);
            if (!$details) return array();

            // リピーターフィールドのデータを整形
            $formatted_details = array();
            foreach ($details as $detail) {
                $formatted_details[] = array(
                    'title' => $detail['detail_title'] ?? '',
                    'description' => $detail['detail_description'] ?? '',
                    'image' => $detail['detail_image'] ? wp_get_attachment_image_url($detail['detail_image'], 'medium') : null
                );
            }

            return $formatted_details;
        },
        'schema' => array(
            'description' => 'Project detail items',
            'type' => 'array',
            'items' => array(
                'type' => 'object',
                'properties' => array(
                    'title' => array('type' => 'string'),
                    'description' => array('type' => 'string'),
                    'image' => array('type' => 'string', 'format' => 'uri')
                )
            ),
            'context' => array('view', 'edit')
        )
    ));
});

独自のエンドポイントを作成する方法(プラグイン・テーマ開発)

標準のWordPress REST APIエンドポイントでは対応できない独自の機能を実装する際は、カスタムエンドポイントを作成します。

基本的なカスタムエンドポイントの作成

<?php
// カスタムエンドポイントの登録
add_action('rest_api_init', function() {
    // 基本的なGETエンドポイント
    register_rest_route('custom/v1', '/hello', array(
        'methods' => 'GET',
        'callback' => 'custom_hello_endpoint',
        'permission_callback' => '__return_true', // 認証不要
    ));

    // パラメータ付きのエンドポイント
    register_rest_route('custom/v1', '/user/(?P<id>\\d+)', array(
        'methods' => 'GET',
        'callback' => 'custom_user_info_endpoint',
        'permission_callback' => 'custom_user_permissions_check',
        'args' => array(
            'id' => array(
                'validate_callback' => function($param) {
                    return is_numeric($param);
                },
                'sanitize_callback' => 'absint',
            ),
        ),
    ));

    // 複数のHTTPメソッドをサポートするエンドポイント
    register_rest_route('custom/v1', '/favorites', array(
        array(
            'methods' => 'GET',
            'callback' => 'get_user_favorites',
            'permission_callback' => 'is_user_logged_in',
        ),
        array(
            'methods' => 'POST',
            'callback' => 'add_user_favorite',
            'permission_callback' => 'is_user_logged_in',
            'args' => array(
                'post_id' => array(
                    'required' => true,
                    'validate_callback' => function($param) {
                        return is_numeric($param) && get_post($param);
                    },
                    'sanitize_callback' => 'absint',
                ),
            ),
        ),
        array(
            'methods' => 'DELETE',
            'callback' => 'remove_user_favorite',
            'permission_callback' => 'is_user_logged_in',
        ),
    ));
});

// エンドポイントのコールバック関数
function custom_hello_endpoint($request) {
    return new WP_REST_Response(array(
        'message' => 'Hello from WordPress REST API!',
        'timestamp' => current_time('mysql'),
        'site_info' => array(
            'name' => get_bloginfo('name'),
            'description' => get_bloginfo('description'),
            'url' => home_url(),
        )
    ), 200);
}

function custom_user_info_endpoint($request) {
    $user_id = $request['id'];
    $user = get_user_by('ID', $user_id);

    if (!$user) {
        return new WP_Error('user_not_found', 'ユーザーが見つかりません', array('status' => 404));
    }

    // 公開情報のみを返す
    return new WP_REST_Response(array(
        'id' => $user->ID,
        'name' => $user->display_name,
        'avatar' => get_avatar_url($user->ID),
        'post_count' => count_user_posts($user->ID),
        'registration_date' => $user->user_registered,
    ), 200);
}

function custom_user_permissions_check($request) {
    // 管理者のみアクセス可能
    return current_user_can('manage_options');
}

// お気に入り機能のエンドポイント
function get_user_favorites($request) {
    $user_id = get_current_user_id();
    $favorites = get_user_meta($user_id, 'favorite_posts', true) ?: array();

    $favorite_posts = array();
    foreach ($favorites as $post_id) {
        $post = get_post($post_id);
        if ($post && $post->post_status === 'publish') {
            $favorite_posts[] = array(
                'id' => $post->ID,
                'title' => $post->post_title,
                'permalink' => get_permalink($post->ID),
                'excerpt' => get_the_excerpt($post),
                'featured_image' => get_the_post_thumbnail_url($post->ID, 'medium'),
            );
        }
    }

    return new WP_REST_Response($favorite_posts, 200);
}

function add_user_favorite($request) {
    $user_id = get_current_user_id();
    $post_id = $request['post_id'];

    $favorites = get_user_meta($user_id, 'favorite_posts', true) ?: array();

    if (!in_array($post_id, $favorites)) {
        $favorites[] = $post_id;
        update_user_meta($user_id, 'favorite_posts', $favorites);

        return new WP_REST_Response(array(
            'message' => 'お気に入りに追加しました',
            'post_id' => $post_id,
            'total_favorites' => count($favorites)
        ), 201);
    }

    return new WP_REST_Response(array(
        'message' => '既にお気に入りに追加されています',
        'post_id' => $post_id
    ), 200);
}

function remove_user_favorite($request) {
    $user_id = get_current_user_id();
    $post_id = $request['post_id'];

    $favorites = get_user_meta($user_id, 'favorite_posts', true) ?: array();
    $key = array_search($post_id, $favorites);

    if ($key !== false) {
        unset($favorites[$key]);
        $favorites = array_values($favorites); // インデックスを再構築
        update_user_meta($user_id, 'favorite_posts', $favorites);

        return new WP_REST_Response(array(
            'message' => 'お気に入りから削除しました',
            'post_id' => $post_id,
            'total_favorites' => count($favorites)
        ), 200);
    }

    return new WP_Error('not_in_favorites', 'お気に入りに登録されていません', array('status' => 404));
}

高度なカスタムエンドポイント:検索機能

<?php
// 高度な検索エンドポイント
add_action('rest_api_init', function() {
    register_rest_route('custom/v1', '/search', array(
        'methods' => 'GET',
        'callback' => 'custom_advanced_search',
        'permission_callback' => '__return_true',
        'args' => array(
            'query' => array(
                'required' => true,
                'validate_callback' => function($param) {
                    return !empty(trim($param));
                },
                'sanitize_callback' => 'sanitize_text_field',
            ),
            'post_type' => array(
                'default' => 'post',
                'validate_callback' => function($param) {
                    return post_type_exists($param);
                },
                'sanitize_callback' => 'sanitize_key',
            ),
            'per_page' => array(
                'default' => 10,
                'validate_callback' => function($param) {
                    return is_numeric($param) && $param > 0 && $param <= 100;
                },
                'sanitize_callback' => 'absint',
            ),
            'page' => array(
                'default' => 1,
                'validate_callback' => function($param) {
                    return is_numeric($param) && $param > 0;
                },
                'sanitize_callback' => 'absint',
            )
        ),
    ));
});

function custom_advanced_search($request) {
    $query_string = $request['query'];
    $post_type = $request['post_type'];
    $per_page = $request['per_page'];
    $page = $request['page'];

    // WP_Queryを使用した高度な検索
    $search_args = array(
        'post_type' => $post_type,
        'post_status' => 'publish',
        'posts_per_page' => $per_page,
        'paged' => $page,
        's' => $query_string,

        // メタクエリも検索対象に含める
        'meta_query' => array(
            'relation' => 'OR',
            array(
                'key' => 'custom_description',
                'value' => $query_string,
                'compare' => 'LIKE'
            ),
            array(
                'key' => 'tags',
                'value' => $query_string,
                'compare' => 'LIKE'
            )
        )
    );

    $search_query = new WP_Query($search_args);

    $results = array();
    if ($search_query->have_posts()) {
        while ($search_query->have_posts()) {
            $search_query->the_post();
            global $post;

            // 検索結果の詳細情報を構築
            $result_item = array(
                'id' => $post->ID,
                'title' => get_the_title(),
                'excerpt' => get_the_excerpt(),
                'permalink' => get_permalink(),
                'date' => get_the_date('c'),
                'author' => array(
                    'id' => $post->post_author,
                    'name' => get_the_author_meta('display_name', $post->post_author),
                ),
                'featured_image' => get_the_post_thumbnail_url($post->ID, 'medium'),
                'post_type' => $post->post_type,
            );

            // カスタムフィールドがある場合は追加
            if (function_exists('get_fields')) {
                $acf_fields = get_fields($post->ID);
                if ($acf_fields) {
                    $result_item['custom_fields'] = $acf_fields;
                }
            }

            $results[] = $result_item;
        }
    }
    wp_reset_postdata();

    // ページネーション情報を含むレスポンス
    $response = new WP_REST_Response(array(
        'results' => $results,
        'total' => $search_query->found_posts,
        'pages' => $search_query->max_num_pages,
        'current_page' => $page,
        'per_page' => $per_page,
        'search_query' => $query_string,
    ), 200);

    // レスポンスヘッダーにページネーション情報を追加
    $response->header('X-WP-Total', $search_query->found_posts);
    $response->header('X-WP-TotalPages', $search_query->max_num_pages);

    return $response;
}

APIレスポンスのカスタマイズ(取得項目追加・JSON編集)

標準のREST APIレスポンスを自分のニーズに合わせてカスタマイズする方法を解説します。

レスポンスデータの追加・削除

<?php
// 投稿のレスポンスをカスタマイズ
add_action('rest_api_init', function() {
    // 閲覧数を追加
    register_rest_field('post', 'view_count', array(
        'get_callback' => function($post) {
            $views = get_post_meta($post['id'], 'post_views_count', true);
            return $views ? intval($views) : 0;
        },
        'schema' => array(
            'description' => '記事の閲覧数',
            'type' => 'integer',
            'context' => array('view', 'edit')
        )
    ));

    // 推定読了時間を追加
    register_rest_field('post', 'reading_time', array(
        'get_callback' => function($post) {
            $content = get_post_field('post_content', $post['id']);
            $word_count = str_word_count(strip_tags($content));
            $reading_time = ceil($word_count / 200); // 1分間に200語として計算

            return array(
                'minutes' => $reading_time,
                'words' => $word_count,
                'formatted' => $reading_time . '分で読めます'
            );
        },
        'schema' => array(
            'description' => '推定読了時間',
            'type' => 'object',
            'context' => array('view')
        )
    ));

    // 関連記事を追加
    register_rest_field('post', 'related_posts', array(
        'get_callback' => function($post) {
            $related_posts = array();
            $post_categories = wp_get_post_categories($post['id']);

            if (!empty($post_categories)) {
                $related_query = new WP_Query(array(
                    'post_type' => 'post',
                    'post_status' => 'publish',
                    'posts_per_page' => 3,
                    'post__not_in' => array($post['id']),
                    'category__in' => $post_categories,
                    'orderby' => 'rand'
                ));

                if ($related_query->have_posts()) {
                    while ($related_query->have_posts()) {
                        $related_query->the_post();
                        global $related_post;

                        $related_posts[] = array(
                            'id' => get_the_ID(),
                            'title' => get_the_title(),
                            'permalink' => get_permalink(),
                            'featured_image' => get_the_post_thumbnail_url(get_the_ID(), 'thumbnail'),
                            'excerpt' => get_the_excerpt()
                        );
                    }
                }
                wp_reset_postdata();
            }

            return $related_posts;
        },
        'schema' => array(
            'description' => '関連記事',
            'type' => 'array',
            'context' => array('view')
        )
    ));
});

レスポンスの完全カスタマイズ

<?php
// 投稿一覧のレスポンス全体をカスタマイズ
add_filter('rest_prepare_post', function($response, $post, $request) {
    $data = $response->get_data();

    // 不要な項目を削除
    unset($data['guid']);
    unset($data['template']);
    unset($data['ping_status']);
    unset($data['comment_status']);

    // カスタム項目を追加
    $data['custom_meta'] = array(
        'seo_title' => get_post_meta($post->ID, '_yoast_wpseo_title', true),
        'seo_description' => get_post_meta($post->ID, '_yoast_wpseo_metadesc', true),
        'social_image' => get_post_meta($post->ID, '_yoast_wpseo_opengraph-image', true),
    );

    // 投稿者情報をより詳細に
    if (isset($data['author'])) {
        $author = get_user_by('ID', $data['author']);
        $data['author_details'] = array(
            'id' => $author->ID,
            'name' => $author->display_name,
            'nickname' => $author->nickname,
            'avatar' => get_avatar_url($author->ID, array('size' => 96)),
            'bio' => get_user_meta($author->ID, 'description', true),
            'website' => $author->user_url,
            'posts_count' => count_user_posts($author->ID),
        );
    }

    // カテゴリー情報をより詳細に
    if (isset($data['categories']) && is_array($data['categories'])) {
        $detailed_categories = array();
        foreach ($data['categories'] as $category_id) {
            $category = get_category($category_id);
            if ($category) {
                $detailed_categories[] = array(
                    'id' => $category->term_id,
                    'name' => $category->name,
                    'slug' => $category->slug,
                    'description' => $category->description,
                    'count' => $category->count,
                    'color' => get_term_meta($category->term_id, 'category_color', true),
                    'link' => get_category_link($category->term_id),
                );
            }
        }
        $data['categories_detailed'] = $detailed_categories;
    }

    $response->set_data($data);
    return $response;
}, 10, 3);

// ユーザー情報のレスポンスをカスタマイズ
add_filter('rest_prepare_user', function($response, $user, $request) {
    $data = $response->get_data();

    // セキュリティ上、メールアドレスを管理者以外には表示しない
    if (!current_user_can('manage_options')) {
        unset($data['email']);
    }

    // カスタムユーザーメタを追加
    $data['social_profiles'] = array(
        'twitter' => get_user_meta($user->ID, 'twitter', true),
        'facebook' => get_user_meta($user->ID, 'facebook', true),
        'linkedin' => get_user_meta($user->ID, 'linkedin', true),
        'github' => get_user_meta($user->ID, 'github', true),
    );

    // 最近の投稿を追加
    $recent_posts = get_posts(array(
        'author' => $user->ID,
        'numberposts' => 3,
        'post_status' => 'publish'
    ));

    $data['recent_posts'] = array_map(function($post) {
        return array(
            'id' => $post->ID,
            'title' => $post->post_title,
            'permalink' => get_permalink($post->ID),
            'date' => $post->post_date,
        );
    }, $recent_posts);

    $response->set_data($data);
    return $response;
}, 10, 3);

パフォーマンス最適化のためのレスポンス制御

<?php
// クエリ最適化とレスポンス軽量化
add_action('rest_api_init', function() {
    // 軽量版の投稿一覧エンドポイント
    register_rest_route('custom/v1', '/posts/light', array(
        'methods' => 'GET',
        'callback' => 'get_posts_light',
        'permission_callback' => '__return_true',
        'args' => array(
            'per_page' => array(
                'default' => 10,
                'sanitize_callback' => 'absint',
            ),
            'page' => array(
                'default' => 1,
                'sanitize_callback' => 'absint',
            )
        ),
    ));
});

function get_posts_light($request) {
    $per_page = $request['per_page'];
    $page = $request['page'];
    
    // 必要最小限のフィールドのみを取得
    $posts = get_posts(array(
        'post_type' => 'post',
        'post_status' => 'publish',
        'numberposts' => $per_page,
        'offset' => ($page - 1) * $per_page,
        'fields' => 'ids' // IDのみを取得してパフォーマンス向上
    ));
    
    $light_posts = array();
    
    foreach ($posts as $post_id) {
        // キャッシュを活用して効率的にデータを取得
        $cache_key = 'light_post_' . $post_id;
        $post_data = wp_cache_get($cache_key, 'custom_posts');
        
        if (false === $post_data) {
            // キャッシュにない場合のみデータベースからデータを取得
            $post = get_post($post_id);
            
            if ($post) {
                $post_data = array(
                    'id' => $post->ID,
                    'title' => array(
                        'rendered' => get_the_title($post->ID)
                    ),
                    'excerpt' => array(
                        'rendered' => wp_trim_words(get_the_excerpt($post), 20)
                    ),
                    'date' => mysql2date('c', $post->post_date, false),
                    'modified' => mysql2date('c', $post->post_modified, false),
                    'slug' => $post->post_name,
                    'status' => $post->post_status,
                    'link' => get_permalink($post->ID),
                    'author' => array(
                        'id' => $post->post_author,
                        'name' => get_the_author_meta('display_name', $post->post_author)
                    ),
                    'featured_media' => array(
                        'id' => get_post_thumbnail_id($post->ID),
                        'url' => get_the_post_thumbnail_url($post->ID, 'medium')
                    ),
                    'categories' => wp_get_post_categories($post->ID, array('fields' => 'names')),
                    'tags' => wp_get_post_tags($post->ID, array('fields' => 'names'))
                );
                
                // カスタムフィールドがある場合は追加
                $custom_fields = get_post_meta($post->ID);
                if (!empty($custom_fields)) {
                    $post_data['custom_fields'] = array();
                    foreach ($custom_fields as $key => $value) {
                        // プライベートフィールド(_で始まる)は除外
                        if (!str_starts_with($key, '_')) {
                            $post_data['custom_fields'][$key] = is_array($value) ? $value[0] : $value;
                        }
                    }
                }
                
                // キャッシュに保存(5分間)
                wp_cache_set($cache_key, $post_data, 'custom_posts', 300);
            }
        }
        
        if ($post_data) {
            $light_posts[] = $post_data;
        }
    }
    
    // ページネーション情報を追加
    $total_posts = wp_count_posts('post')->publish;
    $total_pages = ceil($total_posts / $per_page);
    
    $response = array(
        'posts' => $light_posts,
        'pagination' => array(
            'current_page' => $page,
            'per_page' => $per_page,
            'total_posts' => $total_posts,
            'total_pages' => $total_pages,
            'has_next' => $page < $total_pages,
            'has_prev' => $page > 1
        )
    );
    
    // レスポンスヘッダーにキャッシュ情報を追加
    $wp_rest_response = new WP_REST_Response($response);
    $wp_rest_response->header('Cache-Control', 'public, max-age=300'); // 5分キャッシュ
    $wp_rest_response->header('X-Total-Count', $total_posts);
    $wp_rest_response->header('X-Total-Pages', $total_pages);
    
    return $wp_rest_response;
}
★☆★ VPSなら【ConoHa】で決まり! ★☆★

REST APIが使えない原因とエラー対処法

WordPress REST APIを利用しようとした際に、思うように動作しない場合があります。ここでは、よく発生するエラーとその解決方法について詳しく解説します。

403・401・404・CORSエラーの原因と解決方法

403 Forbidden エラー

原因:

  • ユーザーにAPIアクセス権限がない
  • セキュリティプラグインによるアクセス制限
  • サーバーレベルでのアクセス拒否設定

解決方法:

1.権限の確認

// functions.phpに追加:特定の権限を持つユーザーのみアクセス許可
add_action('rest_api_init', function() {
    if (!current_user_can('edit_posts')) {
        wp_die('API access denied', 'Forbidden', array('response' => 403));
    }
});

2.セキュリティプラグインの設定確認

  • Wordfence、All in One WP Security等の「REST API制限」設定を無効化
  • プラグインの「ホワイトリスト」にAPIエンドポイントを追加

3.htaccessファイルの確認

# 以下のような記述があれば削除またはコメントアウト
# <Files "wp-json">
# Order allow,deny
# Deny from all
# </Files>

401 Unauthorized エラー

原因:

  • 認証情報が不正または不足
  • Application Passwordsが正しく設定されていない
  • 認証トークンの期限切れ

解決方法:

1.Basic認証の確認

# curlでの認証テスト
curl -X GET \
  -H "Authorization: Basic $(echo -n 'username:password' | base64)" \
  https://yoursite.com/wp-json/wp/v2/posts

2.Application Passwordsの再発行

  • WordPress管理画面 > ユーザー > プロフィール
  • 「Application Passwords」で新しいパスワードを生成
  • 生成されたパスワードを認証に使用

3.JWTトークンの更新

// JavaScript例:トークン再取得
fetch('/wp-json/jwt-auth/v1/token', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
    },
    body: JSON.stringify({
        username: 'your_username',
        password: 'your_password'
    })
})

404 Not Found エラー

原因:

  • パーマリンク設定が正しくない
  • REST APIが完全に無効化されている
  • エンドポイントのURLが間違っている

解決方法:

  1. パーマリンク設定の更新
    • WordPress管理画面 > 設定 > パーマリンク
    • 設定を一度変更して「変更を保存」をクリック
    • 元の設定に戻して再度保存
  2. エンドポイントURLの確認 # 正しいエンドポイント例 <https://yoursite.com/wp-json/wp/v2/posts> # 間違った例(よくあるミス) <https://yoursite.com/wp-json/v2/posts> # wpが抜けている
  3. .htaccessの再生成
    • FTPで.htaccessファイルを一時的にリネーム
    • WordPress管理画面でパーマリンク設定を保存
    • 新しい.htaccessが正しく生成されることを確認

CORSエラー(Cross-Origin Resource Sharing)

原因:

  • 異なるドメインからのAPIアクセスがブロックされている
  • サーバーでCORSヘッダーが適切に設定されていない

解決方法:

1.functions.phpでCORSヘッダーを設定

// CORS対応のヘッダーを追加
add_action('rest_api_init', function() {
    remove_filter('rest_pre_serve_request', 'rest_send_cors_headers');
    add_filter('rest_pre_serve_request', function($value) {
        header('Access-Control-Allow-Origin: *');
        header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
        header('Access-Control-Allow-Headers: Content-Type, Authorization');
        return $value;
    });
}, 15);

2.特定ドメインのみ許可する場合

add_action('rest_api_init', function() {
    header('Access-Control-Allow-Origin: https://trusted-domain.com');
    header('Access-Control-Allow-Credentials: true');
});

セキュリティプラグインや.htaccessによるAPI制限の解除方法

主要セキュリティプラグインでの設定

Wordfence Security:

  1. Wordfence > Firewall > Firewall Options
  2. 「Disable WordPress REST API」のチェックを外す
  3. 「Block requests to the WordPress REST API」を無効化
Wordfence Security – Firewall, Malware Scan, and Login Security
ファイアウォール、マルウェアスキャナー、2要素認証、包括的なセキュリティ機能など、当社の24時間体制のチームがサポートします。Wordfence でセキュリティを最優先にしましょう。

.htaccessファイルによる制限解除

確認すべき記述:

# このような記述があればコメントアウトまたは削除
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^wp-json/(.*)$ - [F,L]
</IfModule>

# または
<Files "wp-json">
Order deny,allow
Deny from all
</Files>

# または
RewriteRule ^wp-json - [R=403,L]

正しい.htaccess設定(WordPress標準):

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^index\\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress

サーバーレベルでの制限確認

共用サーバーの場合:

  • コントロールパネルで「ModSecurity」や「WAF設定」を確認
  • REST APIアクセスがルールに引っかかっていないかチェック
  • サーバー管理会社のサポートに確認

VPSやクラウドサーバーの場合:

# Apacheの設定確認
sudo apache2ctl configtest

# Nginxの設定確認
sudo nginx -t

# エラーログの確認
tail -f /var/log/apache2/error.log
# または
tail -f /var/log/nginx/error.log

REST APIが無効化されている場合の再有効化ステップ

ステップ1:現状確認

# APIの応答確認
curl -I <https://yoursite.com/wp-json/>

# レスポンスで200 OKが返れば有効、404や403なら無効化されている

ステップ2:段階的な有効化確認

1.WordPressコア設定の確認

// functions.phpで強制有効化
add_filter('rest_enabled', '__return_true');
add_filter('rest_jsonp_enabled', '__return_true');

2.プラグインの競合チェック

  • すべてのプラグインを一時無効化
  • デフォルトテーマ(Twenty Twenty-Four等)に変更
  • REST APIが動作するか確認
  • 1つずつプラグインを有効化して原因特定

3.テーマの確認

// テーマのfunctions.phpで無効化コードがないか確認
// 以下のような記述があれば削除
add_filter('rest_enabled', '__return_false');
remove_action('rest_api_init', 'rest_api_default_filters', 10, 1);

ステップ3:段階的復旧

1.基本APIエンドポイントの確認

# 投稿一覧の取得テスト
curl https://yoursite.com/wp-json/wp/v2/posts?per_page=1

2.認証が必要なエンドポイントのテスト

# ユーザー情報の取得テスト
curl -H "Authorization: Bearer YOUR_JWT_TOKEN" \
     https://yoursite.com/wp-json/wp/v2/users/me

3.カスタムエンドポイントの復旧

  • プラグインやテーマで作成した独自エンドポイントを順次確認
  • エラーが発生する場合は該当するコードを見直し

ステップ4:最終確認とテスト

// functions.phpで包括的なテスト関数を追加
function check_rest_api_status() {
    if (current_user_can('manage_options')) {
        $test_url = home_url('/wp-json/wp/v2/posts?per_page=1');
        $response = wp_remote_get($test_url);

        if (is_wp_error($response)) {
            echo '<div class="notice notice-error"><p>REST API Error: ' . $response->get_error_message() . '</p></div>';
        } else {
            $code = wp_remote_retrieve_response_code($response);
            if ($code === 200) {
                echo '<div class="notice notice-success"><p>REST API is working correctly!</p></div>';
            } else {
                echo '<div class="notice notice-warning"><p>REST API returned status code: ' . $code . '</p></div>';
            }
        }
    }
}
add_action('admin_notices', 'check_rest_api_status');

これらの手順を順番に実行することで、REST APIが使えない原因を特定し、適切に解決することができます。問題が複雑な場合は、WordPress開発者やサーバー管理者に相談することも検討してください。

よくある質問(FAQ)

REST APIを有効化すると、サイトの表示速度は遅くなる?

通常のサイト表示に対しては、ほとんど影響ありません。

REST APIを有効化しただけでは、既存のWordPressサイトの表示速度に大きな影響を与えることはありません。REST APIは別のエンドポイント(/wp-json/)で動作するため、通常のページ表示処理とは独立しています。

ただし、以下の場合は注意が必要です:

  • 大量のAPIリクエストがある場合:外部アプリケーションからの頻繁なAPI呼び出しがサーバーリソースを消費
  • 複雑なカスタムエンドポイント:処理の重いカスタムAPI関数がサーバーに負荷をかける可能性
  • 認証処理の追加:JWT認証などの処理が若干のオーバーヘッドを生む

パフォーマンス最適化のコツ:

// APIレスポンスにキャッシュヘッダーを追加
add_action('rest_api_init', function() {
add_filter('rest_post_dispatch', function($response, $server, $request) {
$response->header('Cache-Control', 'public, max-age=300'); // 5分キャッシュ
return $response;
}, 10, 3);
});

REST APIを無効化するデメリットは?

多くの現代的なWordPress機能が制限されます。

REST APIを無効化すると、以下のような影響があります:

WordPress標準機能への影響:

  • ブロックエディター(Gutenberg):一部の動的ブロックが正常に動作しない
  • サイトヘルス機能:管理画面の「サイトヘルス」チェックが不完全になる
  • 自動更新機能:プラグインやテーマの自動更新に支障をきたす可能性
  • WordPress.com連携:Jetpackなどのサービス連携が困難になる

プラグインやテーマへの影響:

  • モダンなプラグイン:多くの現代的なプラグインがREST APIに依存
  • カスタムブロック:独自ブロックの多くがAPIを使用
  • フロントエンド投稿機能:ユーザー投稿機能が動作しない
  • リアルタイム機能:チャット、通知、ライブ更新などが不可能

開発・運用面での制約:

// REST API無効化によって使えなくなる例
// フロントエンドからの記事投稿
fetch('/wp-json/wp/v2/posts', {
    method: 'POST',
    // ... この機能が完全に使用不可能
});

代替手段:

完全無効化の代わりに、以下のような部分的制限を推奨します:

// 特定のエンドポイントのみ制限
add_filter('rest_endpoints', function($endpoints) {
    // ユーザー情報APIのみ無効化
    unset($endpoints['/wp/v2/users']);
    unset($endpoints['/wp/v2/users/(?P<id>[\\d]+)']);
    return $endpoints;
});

ヘッドレスCMS化するのに、REST API以外に何が必要?

フロントエンド技術スタック、認証システム、画像最適化などが必要です。

WordPress REST APIは基盤となりますが、完全なヘッドレスCMS化には以下が必要です:

1. フロントエンド開発環境

// Next.js例:WordPress APIからデータ取得
export async function getStaticProps() {
    const res = await fetch('<https://yourwp.com/wp-json/wp/v2/posts>');
    const posts = await res.json();

    return {
        props: { posts },
        revalidate: 60 // ISR:60秒ごとに再生成
    };
}

推奨フレームワーク:

  • Next.js:React基盤、SSG/SSR対応
  • Nuxt.js:Vue.js基盤、高いパフォーマンス
  • Gatsby:GraphQL統合、静的サイト生成

2. 認証・セキュリティシステム

// JWT認証プラグインの設定例
add_filter('jwt_auth_whitelist', function($endpoints) {
    return array(
        '/wp-json/wp/v2/posts',
        '/wp-json/wp/v2/pages',
        '/wp-json/custom/v1/public-data'
    );
});

3. 画像・メディア最適化

  • CDN連携:CloudflareやCloudFrontとの統合
  • 画像最適化:WebP変換、レスポンシブ画像生成
  • メディアAPI拡張:サムネイル生成の自動化
// メディアAPIにカスタムサイズを追加
add_action('rest_api_init', function() {
    register_rest_field('attachment', 'custom_sizes', array(
        'get_callback' => function($object) {
            $sizes = wp_get_attachment_metadata($object['id'])['sizes'] ?? [];
            return $sizes;
        }
    ));
});

4. SEO対策

  • メタデータAPI:Yoast SEOやRankMath連携
  • サイトマップ生成:動的サイトマップの作成
  • 構造化データ:JSON-LD形式での出力

5. プレビュー機能

// 下書きプレビュー用エンドポイント
add_action('rest_api_init', function() {
register_rest_route('custom/v1', '/preview/(?P<id>\\d+)', array(
'methods' => 'GET',
'callback' => 'get_preview_content',
'permission_callback' => function() {
return current_user_can('edit_posts');
}
));
});

REST APIでカスタムフィールドを取得できない時はどうする?

show_in_restパラメータの設定とACFプラグインの活用が効果的です。

Advanced Custom Fields(ACF)を使用している場合:

// ACF設定でREST API出力を有効化
// 管理画面:フィールドグループ設定 > 「REST APIで表示」を「はい」

// またはコードで制御
add_action('acf/init', function() {
    acf_update_setting('show_admin', true);
    acf_update_setting('rest_api_enabled', true);
});

カスタムフィールドの手動登録:

// register_meta()でREST API対応
add_action('init', function() {
    register_meta('post', 'custom_field_name', array(
        'show_in_rest' => true,
        'single' => true,
        'type' => 'string',
    ));
});

既存フィールドをREST APIに追加:

// rest_api_initフックで既存フィールドを拡張
add_action('rest_api_init', function() {
    register_rest_field('post', 'custom_meta', array(
        'get_callback' => function($object) {
            return get_post_meta($object['id'], 'custom_field_name', true);
        },
        'update_callback' => function($value, $object) {
            return update_post_meta($object->ID, 'custom_field_name', $value);
        }
    ));
});

REST APIのセキュリティ対策で最低限やるべきことは?

権限制御、IP制限、レート制限の3つが基本です。

1. 権限ベースのアクセス制御

// 編集権限以上のユーザーのみAPI使用可能
add_filter('rest_authentication_errors', function($result) {
    if (!empty($result)) {
        return $result;
    }

    if (!current_user_can('edit_posts')) {
        return new WP_Error('rest_forbidden', 'API access denied', array('status' => 401));
    }

    return $result;
});

2. 特定エンドポイントの制限

// 機密データへのアクセスを制限
add_filter('rest_endpoints', function($endpoints) {
    // ユーザー一覧の取得を無効化
    if (!current_user_can('list_users')) {
        unset($endpoints['/wp/v2/users']);
    }
    return $endpoints;
});

3. レート制限の実装

// 簡単なレート制限
add_action('rest_api_init', function() {
    $ip = $_SERVER['REMOTE_ADDR'];
    $transient_key = 'api_rate_limit_' . md5($ip);
    $requests = get_transient($transient_key) ?: 0;

    if ($requests > 100) { // 1時間に100リクエスト制限
        wp_die('Rate limit exceeded', 'Too Many Requests', array('response' => 429));
    }

    set_transient($transient_key, $requests + 1, HOUR_IN_SECONDS);
});

推奨セキュリティプラグイン:

  • Application Passwords:WordPress 5.6以降の標準機能
  • JWT Authentication for WP REST API:トークンベース認証
  • WP REST API Controller:包括的なAPI制御

これらの対策により、REST APIを安全に運用することができます。

まとめ

WordPress REST APIの有効化から活用まで、幅広い内容をご紹介してきましたが、いかがでしたでしょうか。現代のWeb開発において、REST APIはもはや「あったら便利」ではなく「必須の技術」となっています。

まず基本的な理解として、WordPress REST APIは単なる「データ取得の仕組み」ではありません。ヘッドレスCMS化、SPA開発、モバイルアプリ連携など、WordPressの可能性を大幅に広げる重要な機能です。特にJavaScriptフレームワークが主流となった現在、APIを通じてデータをやり取りする機会は確実に増えています。

有効化の手順については、決して難しいものではありませんが、正しい知識が必要です。単純に有効化するだけでなく、セキュリティ面での配慮が欠かせません。認証なしでAPIを公開することの危険性や、適切な権限設定の重要性は、実際の運用で痛感される部分でもあります。

重要ポイント

  • REST APIの確認方法/wp-json/にアクセスして正常なJSONレスポンスが返るかチェック
  • セキュリティ第一:認証設定、権限制御、レート制限は必ず実装する
  • エラー対応の基本:403/401/404エラーは原因が明確で、段階的に解決できる
  • プラグイン活用:ACF to REST API、WP REST API Controller等で効率的に管理
  • パフォーマンス配慮:キャッシュ設定とレスポンス最適化で快適なAPI運用を実現
  • 段階的導入:いきなり全面的にヘッドレス化せず、部分的な活用から始める

トラブルシューティングで重要なのは、問題を切り分けて順序立てて解決することです。セキュリティプラグインによる制限、.htaccessの設定ミス、権限不足など、原因は多岐にわたりますが、今回ご紹介した手順に沿って確認すれば、ほとんどの問題は解決できるはずです。

特にCORSエラーについては、フロントエンド開発者の方が頻繁に遭遇する問題です。適切なヘッダー設定を行うことで、安全にクロスオリジンアクセスを許可できます。

ヘッドレスCMS化を検討されている方には、REST APIが入り口に過ぎないことを理解していただきたいと思います。フロントエンド技術、認証システム、画像最適化、SEO対策など、総合的な知識が求められる領域です。しかし、その分だけ得られるメリットも大きく、表示速度の向上、開発効率の改善、ユーザー体験の向上など、多くの恩恵を受けることができます。

最後に、REST APIを活用する際は「セキュリティファースト」の考え方を忘れずにいてください。便利な機能だからこそ、適切な制御が重要になります。今回ご紹介した認証方法や権限設定を参考に、安全で効率的なAPI運用を心がけていただければと思います。

WordPress REST APIの世界は、まだまだ発展途上の分野です。新しい機能や最適化手法が次々と登場していますので、継続的な学習と実践を通じて、より深い理解を目指していただければと思います。皆さんの開発プロジェクトが、より効率的で革新的なものになることを願っています。

あわせて読みたい

【完全ガイド】WordPressのクイック編集にカスタムフィールドを追加する方法|functions.phpコード例付き
WordPressのクイック編集にカスタムフィールドを追加して、投稿一覧から直接編集できる方法を解説します。Quick_edit_custom_boxやsave_postを使ったfunctions.phpへのコード例、複数フィールドやカスタム投稿タイプ対応、バリデーションやUI調整のポイントまで網羅!
WordPressのfunctions.phpで簡単にショートコードを自作!失敗しない基本と応用テクニック
WordPressのfunctions.phpでショートコードを自作する基本から応用、トラブル対策までわかりやすく解説します。初心者でもコピペで使えるテンプレートや引数付きショートコードの作り方、表示されない時の原因と対処法、子テーマでの安全管理方法も紹介。効率的にサイト運用したい方におすすめです。
【徹底解説】.htaccessでWordPressのIP制限を設定する方法 | 強固なセキュリティ対策!
WordPressは世界で最も人気のあるCMSとして、全Webサイトの約40%以上で利用されています。この広範な普及は、同時に悪意ある攻撃者からの標的にもなりやすいことを意味します。あなたのWordPressサイトが小規模であっても、自動化されたボットによる攻撃は日常的に行われています。特に管理画面(wp-admin)...
タイトルとURLをコピーしました