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 は警告の画面出力を疑う