2026年5月17日

2026年5月17日

WordPressの検索機能を拡張する方法

はじめに

「WordPressの検索でカスタム投稿タイプが引っかからない」「カスタムフィールドの値も検索対象にしたい」「リアルタイムのAjax検索を実装したい」——WordPressのデフォルト検索は投稿タイトルと本文のみを対象とするため、拡張が必要なケースが多々あります。

症状・原因

WordPressの検索クエリはデフォルトでpost_type=postの投稿タイトルと本文のみを検索します。カスタム投稿タイプやwp_postmetaに保存されたカスタムフィールドは検索されません。pre_get_postsフックとカスタムJOINで検索範囲を拡張できます。

解決手順

ステップ1:カスタム投稿タイプを検索対象に含める

// functions.php: 検索クエリにカスタム投稿タイプを追加
add_action( 'pre_get_posts', function( WP_Query $query ): void {
    if ( ! $query->is_search() || ! $query->is_main_query() || is_admin() ) {
        return;
    }

    // 検索対象の投稿タイプを指定
    $query->set( 'post_type', [ 'post', 'page', 'news', 'product' ] );

    // 検索結果のソート順
    $query->set( 'orderby', 'relevance' );
} );

// カスタム投稿タイプ登録時に exclude_from_search=false を設定
register_post_type( 'news', [
    'label'               => 'ニュース',
    'public'              => true,
    'exclude_from_search' => false, // 検索対象に含める(デフォルトはpublicと同じ)
    'has_archive'         => true,
    // ...
] );

ステップ2:カスタムフィールドを検索対象に含める

// functions.php: カスタムフィールドを検索対象に追加(JOIN + WHERE を拡張)
class My_Search_Extension {

    public function __construct() {
        add_filter( 'posts_join',  [ $this, 'search_join' ] );
        add_filter( 'posts_where', [ $this, 'search_where' ] );
        add_filter( 'posts_distinct', [ $this, 'search_distinct' ] );
    }

    public function search_join( string $join ): string {
        global $wpdb;
        if ( ! is_search() ) {
            return $join;
        }
        $join .= " LEFT JOIN {$wpdb->postmeta} AS my_meta ON {$wpdb->posts}.ID = my_meta.post_id ";
        return $join;
    }

    public function search_where( string $where ): string {
        global $wpdb;
        if ( ! is_search() ) {
            return $where;
        }

        $search_term = get_search_query();
        $like        = '%' . $wpdb->esc_like( $search_term ) . '%';

        // カスタムフィールドの値も検索対象に追加
        $where = preg_replace(
            '/\(\s*' . $wpdb->posts . '\.post_title\s+LIKE\s*(\'[^\']+\')\s*\)/i',
            '(' . $wpdb->posts . '.post_title LIKE $1) OR (my_meta.meta_value LIKE ' . $wpdb->prepare( '%s', $like ) . ')',
            $where
        );

        return $where;
    }

    public function search_distinct( string $distinct ): string {
        if ( is_search() ) {
            return 'DISTINCT';
        }
        return $distinct;
    }
}
new My_Search_Extension();

ステップ3:検索フォームをカスタマイズする

// searchform.php(テーマルート): カスタム検索フォーム
// get_search_form() はこのファイルを自動的に使用する
?>
<form role="search" method="get" class="search-form" action="<?php echo esc_url( home_url( '/' ) ); ?>">
    <label class="screen-reader-text" for="search-field">
        <?php esc_html_e( 'サイト内検索', 'my-theme' ); ?>
    </label>
    <div class="search-form__inner">
        <input
            type="search"
            id="search-field"
            class="search-form__input"
            placeholder="<?php esc_attr_e( 'キーワードを入力', 'my-theme' ); ?>"
            value="<?php echo esc_attr( get_search_query() ); ?>"
            name="s"
            autocomplete="off"
        >
        <!-- カテゴリーで絞り込み -->
        <select name="cat" class="search-form__category">
            <option value="">すべてのカテゴリー</option>
            <?php
            wp_dropdown_categories( [
                'show_option_none' => '',
                'orderby'          => 'name',
                'hierarchical'     => true,
                'hide_empty'       => true,
                'selected'         => get_query_var( 'cat' ),
            ] );
            ?>
        </select>
        <button type="submit" class="search-form__submit">
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                <circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/>
            </svg>
            <span class="screen-reader-text">検索</span>
        </button>
    </div>
</form>

ステップ4:Ajax検索(インクリメンタルサーチ)を実装する

// functions.php: Ajax検索ハンドラー
add_action( 'wp_ajax_ajax_search',        'my_ajax_search' );
add_action( 'wp_ajax_nopriv_ajax_search', 'my_ajax_search' );

function my_ajax_search(): void {
    check_ajax_referer( 'ajax_search_nonce', 'nonce' );

    $search = sanitize_text_field( wp_unslash( $_GET['q'] ?? '' ) );
    if ( mb_strlen( $search ) < 2 ) {
        wp_send_json_success( [] );
    }

    $query = new WP_Query( [
        'post_type'      => [ 'post', 'page', 'news' ],
        'posts_per_page' => 8,
        's'              => $search,
        'no_found_rows'  => true,
    ] );

    $results = [];
    while ( $query->have_posts() ) {
        $query->the_post();
        $results[] = [
            'title'     => get_the_title(),
            'url'       => get_permalink(),
            'excerpt'   => wp_trim_words( get_the_excerpt(), 20 ),
            'thumbnail' => get_the_post_thumbnail_url( null, 'thumbnail' ) ?: '',
            'type'      => get_post_type_object( get_post_type() )->labels->singular_name,
        ];
    }
    wp_reset_postdata();

    wp_send_json_success( $results );
}
// assets/js/ajax-search.js
const input = document.getElementById('search-field');
if (input) {
    let timer;
    const resultsBox = document.getElementById('search-results');

    input.addEventListener('input', () => {
        clearTimeout(timer);
        const q = input.value.trim();
        if (q.length < 2) { resultsBox.innerHTML = ''; return; }

        timer = setTimeout(async () => {
            const params = new URLSearchParams({ action: 'ajax_search', nonce: mySearch.nonce, q });
            const res    = await fetch(`${mySearch.ajaxUrl}?${params}`);
            const data   = await res.json();

            resultsBox.innerHTML = data.data.map(item => `
                <a href="${item.url}" class="search-result">
                    <span class="search-result__title">${item.title}</span>
                    <span class="search-result__type">${item.type}</span>
                </a>
            `).join('') || '<p class="search-result--empty">結果が見つかりませんでした</p>';
        }, 300);
    });
}

ステップ5:検索結果ページをカスタマイズする

// search.php: 検索結果ページのカスタマイズ
get_header();
?>
<main id="main">
    <header class="search-header">
        <h1>
            <?php
            printf(
                '「%s」の検索結果(%d件)',
                esc_html( get_search_query() ),
                (int) $wp_query->found_posts
            );
            ?>
        </h1>
        <?php get_search_form(); ?>
    </header>

    <?php if ( have_posts() ) : ?>
        <div class="search-results">
            <?php while ( have_posts() ) : the_post(); ?>
                <article class="search-result-item">
                    <h2><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
                    <p class="search-result-item__meta">
                        <span><?php the_time( 'Y年m月d日' ); ?></span>
                        <span><?php echo esc_html( get_post_type_object( get_post_type() )->labels->singular_name ); ?></span>
                    </p>
                    <p><?php echo wp_trim_words( get_the_excerpt(), 30 ); ?></p>
                </article>
            <?php endwhile; ?>
        </div>
        <?php the_posts_pagination(); ?>
    <?php else : ?>
        <p>「<?php echo esc_html( get_search_query() ); ?>」に一致する結果は見つかりませんでした。</p>
    <?php endif; ?>
</main>
<?php
get_footer();

注意事項

  • posts_joinposts_whereフィルターはすべてのクエリに影響するため、必ずis_search()で検索時のみ動作するように制限してください。
  • カスタムフィールドのJOIN拡張はクエリが重くなります。件数が多いサイトではSearchWPなどの専用検索プラグインの使用を検討してください。
  • Ajax検索ではno_found_rows=trueを設定してSQL_CALC_FOUND_ROWSを無効化し、レスポンスを高速化してください。

まとめ

検索機能の拡張は「pre_get_postsでカスタム投稿タイプをpost_typeに追加→posts_join/posts_whereフィルターでカスタムフィールドを検索対象に→searchform.phpでフォームをカスタマイズ→wp_ajax_フックでAjaxインクリメンタルサーチ→search.phpで件数表示と投稿タイプラベルを追加」の流れで整備します。関連記事:WordPressのページネーションをカスタマイズする方法WordPressのカスタムフィールドをプラグインなしで実装する方法

お気軽にご相談ください

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