2026年9月26日
2026年9月26日
WordPressでAjaxを使った動的コンテンツを実装する方法・wp_ajax完全ガイド
はじめに
「ページをリロードせず検索結果を更新したい」「ボタンクリックでコンテンツを動的に読み込みたい」——WordPressのAjax実装はwp_ajaxフックとnonce検証を組み合わせることで、安全に動的コンテンツを実装できます。
症状・原因
WordPressでAjaxを実装する際によくある失敗は、nonce検証の省略・admin-ajax.phpのURLのハードコーディング・wp_ajax_nopriv_フックの忘れです。これらにより「0」や「-1」が返り、Ajaxが動作しない問題が発生します。
解決手順
ステップ1:JavaScriptにAjaxパラメータを渡す
// functions.phpでJavaScriptにデータを渡す
add_action( 'wp_enqueue_scripts', function() {
wp_enqueue_script(
'my-ajax-script',
get_stylesheet_directory_uri() . '/js/ajax.js',
[ 'jquery' ],
'1.0',
true // フッターで読み込み
);
// JavaScriptにURLとnonceを渡す
wp_localize_script( 'my-ajax-script', 'wpAjax', [
'url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'my_ajax_nonce' ),
] );
} );
ステップ2:PHPのAjaxハンドラーを作成する
// ログインユーザー向け
add_action( 'wp_ajax_get_posts_by_category', 'handle_get_posts' );
// 非ログインユーザーも使う場合(必須)
add_action( 'wp_ajax_nopriv_get_posts_by_category', 'handle_get_posts' );
function handle_get_posts() {
// nonce検証(セキュリティ必須)
check_ajax_referer( 'my_ajax_nonce', 'nonce' );
$category = sanitize_text_field( $_POST['category'] ?? '' );
$posts = get_posts( [
'category_name' => $category,
'posts_per_page' => 6,
'post_status' => 'publish',
] );
if ( empty( $posts ) ) {
wp_send_json_error( [ 'message' => '記事が見つかりません。' ] );
}
$data = array_map( function( $post ) {
return [
'id' => $post->ID,
'title' => get_the_title( $post ),
'url' => get_permalink( $post ),
'excerpt' => get_the_excerpt( $post ),
'thumbnail' => get_the_post_thumbnail_url( $post->ID, 'medium' ),
];
}, $posts );
wp_send_json_success( $data );
}
ステップ3:fetch APIでAjaxリクエストを送信する
// js/ajax.js - fetch APIを使ったAjax実装
document.addEventListener('DOMContentLoaded', function() {
const categoryLinks = document.querySelectorAll('.category-filter');
const postsContainer = document.getElementById('posts-container');
categoryLinks.forEach(function(link) {
link.addEventListener('click', async function(e) {
e.preventDefault();
const category = this.dataset.category;
// ローディング表示
postsContainer.innerHTML = '<p class="loading">読み込み中...</p>';
try {
const formData = new FormData();
formData.append('action', 'get_posts_by_category');
formData.append('nonce', wpAjax.nonce);
formData.append('category', category);
const response = await fetch(wpAjax.url, {
method: 'POST',
body: formData,
});
if (!response.ok) throw new Error('Network error');
const data = await response.json();
if (data.success) {
renderPosts(postsContainer, data.data);
} else {
postsContainer.innerHTML = '<p>' + data.data.message + '</p>';
}
} catch (error) {
console.error('Ajax error:', error);
postsContainer.innerHTML = '<p>エラーが発生しました。</p>';
}
});
});
function renderPosts(container, posts) {
container.innerHTML = posts.map(post => `
<article class="post-card">
${post.thumbnail ? `<img src="${post.thumbnail}" alt="">` : ''}
<h3><a href="${post.url}">${post.title}</a></h3>
<p>${post.excerpt}</p>
</article>
`).join('');
}
});
ステップ4:REST APIを使ったモダンなAjax実装
// REST APIにカスタムエンドポイントを追加
add_action( 'rest_api_init', function() {
register_rest_route( 'my-plugin/v1', '/posts', [
'methods' => 'GET',
'callback' => 'rest_get_posts',
'permission_callback' => '__return_true', // 公開エンドポイント
'args' => [
'category' => [
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
],
'per_page' => [
'type' => 'integer',
'default' => 6,
],
],
] );
} );
function rest_get_posts( WP_REST_Request $request ) {
$posts = get_posts( [
'category_name' => $request->get_param( 'category' ),
'posts_per_page' => $request->get_param( 'per_page' ),
] );
return array_map( function( $post ) {
return [
'id' => $post->ID,
'title' => get_the_title( $post ),
'url' => get_permalink( $post ),
'excerpt' => get_the_excerpt( $post ),
];
}, $posts );
}
// REST APIを使う場合(nonceはヘッダーで送信)
const response = await fetch('/wp-json/my-plugin/v1/posts?category=news', {
headers: {
'X-WP-Nonce': wpApiSettings.nonce, // wp_localize_scriptで渡したnonce
},
});
const posts = await response.json();
ステップ5:Ajaxのデバッグ方法
// Ajaxレスポンスが0や-1になる場合のチェックリスト
// 1. アクション名の確認
// add_action( 'wp_ajax_{action}', ... ) の {action} と
// FormDataの action フィールドが一致しているか?
// 2. nonce の確認
// wp_create_nonce( 'my_nonce' ) と
// check_ajax_referer( 'my_nonce', 'nonce' ) のキーが一致しているか?
// 3. nopriv フックの確認
// 非ログインユーザーが使う場合 wp_ajax_nopriv_ も必要
// デバッグ用: 全POSTデータをログ出力
add_action( 'wp_ajax_my_debug', function() {
error_log( print_r( $_POST, true ) );
wp_send_json_success( 'debug ok' );
} );
注意事項
check_ajax_referer()は省略しないでください。nonceなしでAjaxを実装すると、CSRF攻撃により第三者がリクエストを送信できてしまいます。wp_send_json_success()は自動的にwp_die()を呼び出します。ハンドラー関数の最後に明示的なdie()は不要です(二重呼び出し防止)。- admin-ajax.phpはすべてのリクエストでWordPressを完全にブートするため、高頻度のAjaxリクエストにはREST APIの使用を推奨します。REST APIの方がキャッシュ・並列処理の面で有利です。
まとめ
WordPressのAjax実装は「wp_localize_scriptでURLとnonce渡し→wp_ajax_フックでハンドラー登録→check_ajax_refererでnonce検証→fetch APIでリクエスト→wp_send_json_successでレスポンス」の流れが基本です。高パフォーマンスが必要な場合はREST APIの採用を検討してください。関連記事:WordPressのREST APIを活用する方法、WordPressのページネーションをカスタマイズする方法。