結論
admin-ajax.php が 0 や HTTP 400 を返すときは、ほとんどの場合「リクエストの action に対応するフックが登録されていない」ことが原因です。次の順に確認すると、短時間で原因を切り分けられます。
- 送信している
actionの値と、add_action( 'wp_ajax_◯◯' )の◯◯が完全一致しているか。 - ログインしていない訪問者からのリクエストなのに、
wp_ajax_nopriv_◯◯を登録していないのではないか。 - ハンドラー関数が、functions.php やプラグイン本体から実際に読み込まれているか(管理画面のみで読むファイルに書いていないか)。
-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 を返す原因と対処の関連項目
- check_ajax_referer() の使い方|説明・引数・注意点
- wp_ajax_nopriv_{action} フックの使い方|説明・引数・注意点
- wp_send_json_success() の使い方|説明・引数・注意点
- wp_send_json_error() の使い方|説明・引数・注意点
- wp_doing_ajax() の使い方|説明・引数・注意点
