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のページネーションをカスタマイズする方法。

お気軽にご相談ください

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