2026年8月9日

2026年8月9日

WordPressのwp_die()とwp_send_json_error()を使い分ける安全なエラーハンドリング

WordPressで処理を途中で止めてエラーを返すとき、画面(HTML)を返す場面では wp_die()、Ajax で JSON を返す場面では wp_send_json_error()、REST API では WP_Error を return する、と使い分けるのが基本です。この記事では、それぞれの動きの違い、nonce と権限チェックを含めた安全な書き方、そしてブラウザーに「0」や「-1」しか返ってこないときの原因の切り分け方を解説します。

PHP の例外(try-catch)をプラグイン内部でどう扱うかは、別記事のテーマとして分けています。この記事は「利用者やブラウザーにエラーをどう返すか」に絞っています。

3つの返し方と使う場面

WordPress には、処理を止めてエラーを伝える方法が複数あり、呼ばれる場面によって適したものが違います。

  • **wp_die()**:管理画面やフォーム送信(admin_post_ アクションなど)で、人が見る画面にエラーを出して処理を終える
  • **wp_send_json_error()**:admin-ajax.php 経由の Ajax 処理で、JavaScript が読む JSON を返して処理を終える
  • **WP_Error を return**:REST API のコールバックで、ステータスコード付きのエラーを返す(終了処理は REST API 側が行う)

迷ったら「受け取るのは人か、プログラムか」で考えます。人が見る画面なら wp_die()、JavaScript が受け取るなら JSON、REST API なら WP_Error です。

wp_die() の基本

wp_die( $message, $title, $args ) は、メッセージを表示して PHP の実行を終えます。第3引数の response で HTTP ステータスコードを指定でき、back_link を true にすると「戻る」リンクが付きます。第2引数や第3引数に数値を渡すと、ステータスコードとして扱われます。

<?php
add_action( 'admin_post_myplugin_export', 'myplugin_handle_export' );

function myplugin_handle_export() {
    // 1. 権限チェック(ログインしているだけでは不十分)
    if ( ! current_user_can( 'manage_options' ) ) {
        wp_die(
            esc_html__( 'この操作を行う権限がありません。', 'myplugin' ),
            esc_html__( '権限エラー', 'myplugin' ),
            array(
                'response'  => 403,
                'back_link' => true,
            )
        );
    }

    // 2. nonce チェック(失敗すると WordPress 標準の確認画面で終了する)
    check_admin_referer( 'myplugin_export' );

    // 3. ここから本来の処理
}

注意したいのは、wp_die() の既定の出力は HTML だという点です。メッセージに利用者の入力やデータベースの値を含める場合は、esc_html() などでエスケープしてから渡します。また、SQL のエラー文やファイルパスのような内部情報はメッセージに含めず、error_log() でサーバー側のログに残し、画面には一般的な文言だけを出します。

wp_die() は内部で「ハンドラー」を切り替えています。Ajax リクエスト中(wp_doing_ajax() が true)は HTML ではなくメッセージだけを出す Ajax 用ハンドラーが使われ、JSON を要求するリクエストでは JSON 用ハンドラーが使われます。どのハンドラーを使うかは wp_die_handler、wp_die_ajax_handler、wp_die_json_handler などのフィルターで差し替えられますが、通常は触る必要はありません。

wp_send_json_error() の基本

wp_send_json_error( $data, $status_code ) は、次の形の JSON を出力して処理を終えます。

{ "success": false, "data": { "code": "invalid_nonce", "message": "..." } }

成功時は wp_send_json_success() を使い、success が true になります。JavaScript 側は HTTP ステータスと success の両方を見て判定できます。

ここでつまずきやすいのが、第2引数のステータスコードです。省略すると HTTP ステータスは 200 のままで、JSON の中だけが失敗を示します。監視ツールやブラウザーの開発者ツールで失敗を見分けやすくするため、入力エラーは 400、権限や nonce の失敗は 403、サーバー側の失敗は 500 のように指定しておくと、後の調査が楽になります。

$data に WP_Error を渡すこともでき、その場合は code と message の組の配列に変換されて出力されます。

安全な Ajax ハンドラーの例

<?php
add_action( 'wp_ajax_myplugin_save_excerpt', 'myplugin_save_excerpt' );

function myplugin_save_excerpt() {
    // 第3引数 false で、失敗時に自動終了させず自分で JSON を返す
    if ( ! check_ajax_referer( 'myplugin_save_excerpt', 'nonce', false ) ) {
        wp_send_json_error(
            array(
                'code'    => 'invalid_nonce',
                'message' => __( 'ページを再読み込みしてから、もう一度お試しください。', 'myplugin' ),
            ),
            403
        );
    }

    $post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;
    $excerpt = isset( $_POST['excerpt'] ) ? sanitize_textarea_field( wp_unslash( $_POST['excerpt'] ) ) : '';

    if ( ! $post_id || '' === $excerpt ) {
        wp_send_json_error(
            array(
                'code'    => 'invalid_input',
                'message' => __( '入力内容を確認してください。', 'myplugin' ),
            ),
            400
        );
    }

    // 投稿ごとの権限を確認する
    if ( ! current_user_can( 'edit_post', $post_id ) ) {
        wp_send_json_error(
            array(
                'code'    => 'forbidden',
                'message' => __( 'この投稿を編集する権限がありません。', 'myplugin' ),
            ),
            403
        );
    }

    $result = wp_update_post(
        array(
            'ID'           => $post_id,
            'post_excerpt' => $excerpt,
        ),
        true // 失敗時に WP_Error を返させる
    );

    if ( is_wp_error( $result ) ) {
        // 詳細はログへ。画面には一般的な文言だけを返す
        error_log( 'myplugin_save_excerpt: ' . $result->get_error_message() );
        wp_send_json_error(
            array(
                'code'    => 'save_failed',
                'message' => __( '保存に失敗しました。時間をおいて再度お試しください。', 'myplugin' ),
            ),
            500
        );
    }

    wp_send_json_success( array( 'message' => __( '保存しました。', 'myplugin' ) ) );
}

check_ajax_referer() を第3引数なしで呼ぶと、失敗時に -1 という文字列とステータス 403 を返して終了します。JSON ではないため、JavaScript 側で JSON として読もうとすると解析エラーになります。JSON で統一したい場合は、上のように false を渡して自分でエラーを返します。

順番にも意味があります。nonce で「この画面から送られたリクエストか」を確かめ、入力を検証し、最後に「この投稿を操作してよい人か」を確かめます。nonce は権限の代わりにはならないので、current_user_can() は省略しないでください。

JavaScript 側の受け取り方

nonce と送信先 URL は、スクリプトを読み込むときに渡します。

<?php
add_action( 'admin_enqueue_scripts', function () {
    wp_enqueue_script( 'myplugin-admin', plugins_url( 'admin.js', __FILE__ ), array(), '1.0.0', true );
    wp_add_inline_script(
        'myplugin-admin',
        'const mypluginAjax = ' . wp_json_encode( array(
            'url'   => admin_url( 'admin-ajax.php' ),
            'nonce' => wp_create_nonce( 'myplugin_save_excerpt' ),
        ) ) . ';',
        'before'
    );
} );
async function saveExcerpt( postId, excerpt ) {
  const body = new URLSearchParams( {
    action: 'myplugin_save_excerpt',
    nonce: mypluginAjax.nonce,
    post_id: postId,
    excerpt: excerpt,
  } );
  const res = await fetch( mypluginAjax.url, { method: 'POST', credentials: 'same-origin', body } );

  let json = null;
  try {
    json = await res.json();
  } catch ( e ) {
    // "0" や "-1"、PHP の警告や HTML が返ってきた場合はここに来る
  }

  if ( ! json || ! json.success ) {
    const message = json && json.data && json.data.message ? json.data.message : '通信エラーが発生しました。';
    alert( message ); // 実際は画面内の通知領域に表示するのが望ましい
    return;
  }
  // 成功時の処理
}

fetch() は 4xx や 5xx でも例外を投げないため、res.ok か JSON の success で判定する必要があります。メッセージを画面に差し込むときは textContent を使い、innerHTML に入れないようにすると、意図しない HTML の解釈を防げます。

REST API では WP_Error を返す

register_rest_route() で作ったエンドポイントの中では、wp_send_json_error() や wp_die() を使わず、WP_Error を return します。

<?php
return new WP_Error(
    'myplugin_invalid_input',
    __( '入力内容を確認してください。', 'myplugin' ),
    array( 'status' => 400 )
);

REST API 側がステータスコードと JSON の形を整えてくれるので、フィルターやログ、テストとの相性がよくなります。REST API の処理中に wp_send_json() 系の関数を呼ぶと、開発時に「使い方が正しくない」という通知(_doing_it_wrong)が出る場合があります。権限は permission_callback で、入力は args の sanitize_callback と validate_callback で扱うのが基本です。

「0」「-1」しか返ってこないときの切り分け

ブラウザーの開発者ツールの「ネットワーク」タブで、admin-ajax.php へのリクエストを選び、ステータスコードとレスポンス本文を確認します。よくある原因を、起きやすい順に並べます。

1. 本文が「0」、ステータス 400

admin-ajax.php が、送られてきた action に対応するフックを見つけられなかった状態です。次を確認します。

  • JavaScript で送っている action の値と、wp_ajax_ の後ろの名前が一致しているか
  • ログアウト状態の利用者から呼ぶなら、wp_ajax_nopriv_ 側にもフックを登録しているか
  • add_action() を、テンプレートファイルの中など Ajax リクエスト時には読み込まれない場所に書いていないか(プラグイン本体か functions.php の最上位で登録する)

2. 本文が「-1」、ステータス 403

nonce の検証に失敗しています。check_ajax_referer() に渡したアクション名と、wp_create_nonce() のアクション名が同じか、送信しているパラメーター名(例では nonce)が一致しているかを確認します。ページを長時間開いたままにして nonce の有効期限が切れた場合や、ページキャッシュで古い nonce が配られている場合にも起きます。

3. JSON の前に警告文や HTML が混じる

PHP の Notice や Warning が画面に出力され、JSON が壊れている状態です。本番環境では WP_DEBUG_DISPLAY を false にし、WP_DEBUG_LOG でログファイルに記録するよう設定します。そのうえで、ログに出ている警告の原因を直します。ファイル先頭の空白や BOM(文字コードの印)が出力されている場合も同じ症状になります。

4. 重大なエラーの HTML が返る

PHP の致命的エラーで処理が止まっています。サーバーのエラーログか wp-content/debug.log を開き、PHP Fatal error の行にあるファイル名と行番号から原因のコードを特定します。

5. ステータス 200 なのに失敗扱い

wp_send_json_error() にステータスコードを渡していない場合の正常な動きです。必要なら第2引数を指定します。

本番で気をつけること

  • エラーメッセージに内部情報(SQL、パス、スタックトレース)を出さない。詳細は error_log() へ
  • ログにもパスワード、トークン、個人情報を書き込まない
  • wp_die() や wp_send_json_*() の後のコードは実行されない。後片付けが必要な処理は、呼び出す前に済ませる
  • 権限チェックと nonce チェックを省略したハンドラーを公開しない
  • コードを変更するときは、子テーマか自作プラグインに書き、ステージング環境で確認してから本番に反映する

関数の引数や内部のハンドラーの仕組みは、WordPress のバージョンによって追加や変更があります(2026年10月現在)。使う前に、利用中のバージョンの開発者向けリファレンスで引数を確認してください。

まとめ

  • 人が見る画面には wp_die()、Ajax には wp_send_json_error()、REST API には WP_Error の return を使う
  • wp_die() のメッセージはエスケープし、response でステータスコードを指定する
  • wp_send_json_error() は第2引数でステータスコードを指定すると、失敗を見分けやすくなる
  • nonce の確認、入力の検証、current_user_can() による権限確認をセットで書く
  • 「0」はフック未登録、「-1」は nonce の失敗、壊れた JSON は警告の画面出力を疑う

お気軽にご相談ください

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