admin-ajax.php が 0 や 400 を返す原因と対処|Ajax の書き方

結論

admin-ajax.php が 0 や HTTP 400 を返すときは、ほとんどの場合「リクエストの action に対応するフックが登録されていない」ことが原因です。次の順に確認すると、短時間で原因を切り分けられます。

  1. 送信している action の値と、add_action( 'wp_ajax_◯◯' ) の ◯◯ が完全一致しているか。
  2. ログインしていない訪問者からのリクエストなのに、wp_ajax_nopriv_◯◯ を登録していないのではないか。
  3. ハンドラー関数が、functions.php やプラグイン本体から実際に読み込まれているか(管理画面のみで読むファイルに書いていないか)。
  4. -1(403)が返るなら、それは nonce 検証の失敗で、フック未登録とは別の問題。

原因・仕組み

admin-ajax.php は、受け取った action から wp_ajax_{action}(ログイン中)または wp_ajax_nopriv_{action}(未ログイン)というアクションを実行するだけの入口です。公式ハンドブックによると、リクエストには必ず action を含め、バックエンドでは ajaxurl が使え、フロントでは URL を自分で JS に渡す必要があります。

本検証環境(WordPress 7.1.2)の wp-admin/admin-ajax.php を読むと、分岐は次のとおりでした。

状況 admin-ajax.php の動作 該当行
action が空 wp_die( '0', 400 ) 32行目
ログイン中で wp_ajax_{action} が未登録 wp_die( '0', 400 ) 180〜181行目
未ログインで wp_ajax_nopriv_{action} が未登録 wp_die( '0', 400 ) 195〜196行目
ハンドラーが何も出力せず終了した wp_die( '0' ) 211行目

つまり「0」の正体は、ハンドラーが無い、またはハンドラーが何も返さずに終わった、という合図です。ハンドラー未登録のときのステータスは、このバージョンのソースでは 400 です。古い解説にある「ステータス 200 で 0」という説明とは食い違うことがあるので、実際のステータスは開発者ツールで確認してください。

手順(サンプルコード)

ログイン済み・未ログインの両方から呼べる「いいね」処理の最小例です。入力は absint で整数化し、nonce を必ず検証します(check_ajax_referer() の使い方|説明・引数・注意点、wp_send_json_success() の使い方|説明・引数・注意点 参照)。

add_action( 'wp_ajax_ks_like', 'ks_like_handler' );
add_action( 'wp_ajax_nopriv_ks_like', 'ks_like_handler' );

function ks_like_handler() {
    check_ajax_referer( 'ks_like_nonce', 'nonce' ); // 失敗時は -1 / 403 で終了
    $post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;
    if ( ! $post_id ) {
        wp_send_json_error( array( 'message' => 'post_id が不正です' ), 400 );
    }
    // ここで保存処理を行う(権限が必要な処理なら current_user_can() も確認)
    wp_send_json_success( array( 'post_id' => $post_id ) ); // 内部で wp_die() 済み
}

add_action( 'wp_enqueue_scripts', function () {
    wp_enqueue_script( 'ks-like', get_theme_file_uri( 'js/like.js' ), array(), '1.0', true );
    wp_add_inline_script( 'ks-like', 'window.ksLike = ' . wp_json_encode( array(
        'ajaxUrl' => admin_url( 'admin-ajax.php' ),
        'nonce'   => wp_create_nonce( 'ks_like_nonce' ),
    ) ) . ';', 'before' );
} );

JavaScript 側は、action と nonce を必ず送ります。

fetch( ksLike.ajaxUrl, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams( { action: 'ks_like', nonce: ksLike.nonce, post_id: 12 } ),
} ).then( ( r ) => r.json() ).then( console.log );

動作確認(検証環境と結果)

検証環境は WordPress 7.1.2、PHP 8.2.12、MariaDB 10.4.32、実行は php C:Tempwp-testrun.php admin-ajax-returns-0.php です。admin-ajax.php 自体は HTTP で呼ばず、wp_die を差し替えて分岐を再現しました(ログは tests フォルダに保存)。

  • has_action( 'wp_ajax_ks_like' ) は登録済み、綴りを間違えた wp_ajax_ks_typo は未登録でした。
  • nonce が不正なとき: wp_die のメッセージは -1、ステータスは 403。
  • post_id なしのとき: {"success":false,"data":{"message":"post_id が不正です"}}。
  • 正常時: {"success":true,"data":{"post_id":7}}。

実際のブラウザからの HTTP 呼び出しは、この環境では実行していません。400 の根拠は admin-ajax.php のソースの読み取りに加え、レビュー時に PHP 内蔵サーバー経由で未ログインのまま admin-ajax.php へ POST した結果です(action=ks_nohook(未登録)、action 空、action なしの3通りとも、ステータス 400・本文 0)。ログイン中の 400 は未検証です。

admin-ajax.php が 0 や 400 を返す原因と対処の注意点

  • wp_ajax_nopriv_ は「未ログイン用」であり、ログイン中のユーザーには wp_ajax_ 側が使われます。両方に同じ関数を登録するのが一般的です。
  • 管理画面以外から admin-ajax.php を呼ぶ処理は、誰でも叩けると考えて、入力検証と権限確認を厳密に行います。
  • キャッシュやセキュリティ系プラグインが admin-ajax.php への POST を遮断すると、フック以前の問題で失敗します。ブラウザの開発者ツールのネットワークタブでステータスを確認してください。
  • 新規実装では、REST API も候補です(register_rest_route() の使い方|説明・引数・注意点)。

admin-ajax.php が 0 や 400 を返す原因と対処でよくあるミス

  • JS で action: 'ks-like'、PHP で ks_like のように、ハイフンとアンダースコアが違う。
  • ハンドラーの最後で wp_die() や wp_send_json_* を呼ばず、echo のあとに処理が続いて余計な 0 が付く。
  • $_POST ではなく $_GET で値を受け取っている。
  • nonce をページに埋め込んだ後に、ページキャッシュで古い nonce を配信している。

admin-ajax.php が 0 や 400 を返す原因と対処のチェックリスト

  • action の文字列は JS と PHP で一致しているか。
  • 未ログインで使う処理に wp_ajax_nopriv_ を登録したか。
  • ハンドラーを定義するファイルが、Ajax リクエスト時にも読み込まれるか。
  • nonce 検証、入力のサニタイズ、権限チェックを入れたか。
  • ハンドラーが wp_send_json_* または wp_die() で終わっているか。

admin-ajax.php が 0 や 400 を返す原因と対処のFAQ(よくある質問)

Q. 0 と -1 の違いは?
A. このバージョンのソースでは、ハンドラー無しや無出力は 0、check_ajax_referer() の失敗は -1(403)です。

Q. admin-ajax.php の URL は、フロントでどう渡しますか。
A. admin_url( 'admin-ajax.php' ) を wp_add_inline_script や wp_localize_script で JS に渡します(wp_localize_script() の使い方|説明・引数・注意点)。

Q. WordPress 以外のホスト(別ドメイン)から呼べますか。
A. CORS やクッキーの制約があるため、別ドメインからの利用は REST API を検討してください。

筆者の見解(admin-ajax.php が 0 や 400 を返す原因と対処)

admin-ajax.php のトラブルは、コードの難しさより「名前の一致」と「どのフックが呼ばれるか」の見落としが大半だと考えます。まず has_action() で登録状況を確かめ、次に nonce、最後に出力の順で見るのが近道です。新規開発では、権限や検証を宣言的に書ける REST API を優先し、既存資産の保守に admin-ajax.php を使う、という切り分けが現実的だと思います。

admin-ajax.php が 0 や 400 を返す原因と対処の関連項目

出典(一次情報)

タイトルとURLをコピーしました