2026年6月18日
2026年6月18日
WordPressの検索機能をカスタマイズする方法
はじめに
「WordPressの検索が投稿タイトルしかヒットしない」「特定の投稿タイプだけ検索したい」「検索結果ページのデザインを改善したい」——pre_get_postsフックを使うとWordPressの検索動作を柔軟にカスタマイズできます。
症状・原因
WordPressのデフォルト検索はタイトルと本文のみを対象とし、カスタムフィールドやカスタム投稿タイプのコントロールが難しいです。pre_get_postsとAjaxを組み合わせることでUXを大幅に改善できます。
解決手順
ステップ1:pre_get_postsで検索対象を絞り込む
// functions.php: 検索対象の投稿タイプと並び順を制御
add_action( 'pre_get_posts', function( WP_Query $query ) {
// 管理画面・メインクエリ以外は対象外
if ( is_admin() || ! $query->is_main_query() ) return;
if ( $query->is_search() ) {
// 検索対象の投稿タイプを指定
$query->set( 'post_type', [ 'post', 'portfolio', 'faq' ] );
// 検索結果の並び順
$query->set( 'orderby', 'relevance' ); // relevance / date / title
// 1ページの表示件数
$query->set( 'posts_per_page', 20 );
// 特定カテゴリーを除外
$query->set( 'category__not_in', [ 5, 12 ] ); // カテゴリーIDで除外
}
} );
ステップ2:カスタムフィールドも検索対象に含める
// functions.php: カスタムフィールドを検索対象に追加
add_filter( 'posts_search', function( $search, WP_Query $query ) {
global $wpdb;
if ( ! $query->is_search() || ! $query->is_main_query() ) {
return $search;
}
$search_term = $query->get( 's' );
if ( empty( $search_term ) ) return $search;
$like = '%' . $wpdb->esc_like( $search_term ) . '%';
// カスタムフィールド「price」「description」も検索対象に
$meta_search = $wpdb->prepare(
" OR EXISTS (
SELECT 1 FROM {$wpdb->postmeta}
WHERE {$wpdb->postmeta}.post_id = {$wpdb->posts}.ID
AND {$wpdb->postmeta}.meta_key IN ('product_description', 'client_name')
AND {$wpdb->postmeta}.meta_value LIKE %s
)",
$like
);
$search .= $meta_search;
return $search;
}, 10, 2 );
// JOINを追加してDISTINCTで重複を排除
add_filter( 'posts_distinct', function( $distinct, WP_Query $query ) {
if ( $query->is_search() && $query->is_main_query() ) {
return 'DISTINCT';
}
return $distinct;
}, 10, 2 );
ステップ3:search.phpで検索結果ページをデザインする
<?php
// search.php: カスタム検索結果ページ
get_header();
$search_query = get_search_query();
$found_posts = $GLOBALS['wp_query']->found_posts;
?>
<main class="search-results-page">
<div class="container">
<header class="search-header">
<h1 class="search-title">
<?php if ( $search_query ) : ?>
「<strong><?php echo esc_html( $search_query ); ?></strong>」の検索結果
<span class="found-count"><?php echo esc_html( $found_posts ); ?> 件</span>
<?php else : ?>
検索
<?php endif; ?>
</h1>
<!-- 検索フォームを再表示 -->
<div class="search-form-wrap">
<?php get_search_form(); ?>
</div>
</header>
<?php if ( have_posts() ) : ?>
<div class="search-results">
<?php while ( have_posts() ) : the_post(); ?>
<article class="search-result-item">
<div class="result-meta">
<span class="post-type-badge">
<?php echo esc_html( get_post_type_object( get_post_type() )->labels->singular_name ); ?>
</span>
<time datetime="<?php echo get_the_date( 'c' ); ?>">
<?php the_date(); ?>
</time>
</div>
<h2 class="result-title">
<a href="<?php the_permalink(); ?>">
<?php the_title(); ?>
</a>
</h2>
<?php if ( has_post_thumbnail() ) : ?>
<div class="result-thumbnail">
<?php the_post_thumbnail( 'thumbnail' ); ?>
</div>
<?php endif; ?>
<div class="result-excerpt">
<?php the_excerpt(); ?>
</div>
</article>
<?php endwhile; ?>
</div>
<?php the_posts_pagination( [
'prev_text' => '← 前のページ',
'next_text' => '次のページ →',
] ); ?>
<?php else : ?>
<div class="no-results">
<p>「<?php echo esc_html( $search_query ); ?>」に一致するコンテンツが見つかりませんでした。</p>
<p>別のキーワードをお試しください。</p>
</div>
<?php endif; ?>
</div>
</main>
<?php get_footer(); ?>
ステップ4:Ajax検索(インクリメンタルサーチ)を実装する
// functions.php: Ajax検索エンドポイントを登録
add_action( 'wp_ajax_live_search', 'my_live_search' );
add_action( 'wp_ajax_nopriv_live_search', 'my_live_search' );
function my_live_search() {
check_ajax_referer( 'live_search_nonce', 'nonce' );
$keyword = sanitize_text_field( $_POST['keyword'] ?? '' );
if ( strlen( $keyword ) < 2 ) {
wp_send_json_success( [] );
}
$results = get_posts( [
's' => $keyword,
'posts_per_page' => 8,
'post_type' => [ 'post', 'portfolio' ],
'post_status' => 'publish',
] );
$data = array_map( function( $post ) {
return [
'id' => $post->ID,
'title' => esc_html( $post->post_title ),
'url' => get_permalink( $post->ID ),
'thumbnail' => get_the_post_thumbnail_url( $post->ID, 'thumbnail' ),
'type' => get_post_type_object( $post->post_type )->labels->singular_name,
];
}, $results );
wp_send_json_success( $data );
}
// JavaScriptファイルをエンキュー
add_action( 'wp_enqueue_scripts', function() {
wp_enqueue_script( 'live-search', get_stylesheet_directory_uri() . '/js/live-search.js', [], '1.0', true );
wp_localize_script( 'live-search', 'liveSearch', [
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'live_search_nonce' ),
] );
} );
// js/live-search.js: インクリメンタルサーチ
(function() {
const input = document.querySelector('.search-field');
const form = input?.closest('form');
if (!input || !form) return;
let dropdown = null;
let timer = null;
function createDropdown() {
if (dropdown) return;
dropdown = document.createElement('ul');
dropdown.className = 'live-search-dropdown';
form.style.position = 'relative';
form.appendChild(dropdown);
}
function removeDropdown() {
if (dropdown) { dropdown.remove(); dropdown = null; }
}
input.addEventListener('input', function() {
clearTimeout(timer);
const keyword = this.value.trim();
if (keyword.length < 2) { removeDropdown(); return; }
timer = setTimeout(async function() {
const body = new FormData();
body.append('action', 'live_search');
body.append('keyword', keyword);
body.append('nonce', liveSearch.nonce);
const res = await fetch(liveSearch.ajaxUrl, { method: 'POST', body });
const json = await res.json();
if (!json.success || !json.data.length) { removeDropdown(); return; }
createDropdown();
dropdown.innerHTML = json.data.map(item => `
<li>
<a href="${item.url}">
${item.thumbnail ? `<img src="${item.thumbnail}" alt="">` : ''}
<span class="result-type">${item.type}</span>
<span class="result-title">${item.title}</span>
</a>
</li>
`).join('');
}, 300);
});
document.addEventListener('click', function(e) {
if (!form.contains(e.target)) removeDropdown();
});
})();
ステップ5:検索フォームのHTMLをカスタマイズする
// functions.php: get_search_form()の出力をカスタマイズ
add_filter( 'get_search_form', function( $form ) {
$form = '<form role="search" method="get" class="search-form" action="' . esc_url( home_url( '/' ) ) . '">
<div class="search-input-wrap">
<label class="screen-reader-text" for="search-field">検索:</label>
<input
type="search"
id="search-field"
class="search-field"
placeholder="キーワードを入力..."
value="' . esc_attr( get_search_query() ) . '"
name="s"
autocomplete="off"
aria-label="サイト内検索"
>
<button type="submit" class="search-submit" aria-label="検索する">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="20" height="20" aria-hidden="true">
<path d="M21 21l-4.35-4.35M17 11A6 6 0 1 1 5 11a6 6 0 0 1 12 0z" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>
</svg>
</button>
</div>
</form>';
return $form;
} );
注意事項
- カスタムフィールド検索を
posts_searchフィルターで実装する場合、JOINの重複や重複レコードが発生しやすいためDISTINCTを忘れずに追加してください。 - Ajax検索のエンドポイントは必ず
check_ajax_referer()でNonceを検証してください。Nonceなしのエンドポイントはスパムリクエストの踏み台になりえます。 - 検索キーワードは必ず
sanitize_text_field()またはget_search_query()でサニタイズしてから使用してください。
まとめ
WordPressの検索カスタマイズは「pre_get_postsで投稿タイプ・件数を制御→posts_searchフィルターでカスタムフィールドを検索対象に追加→search.phpで結果ページをデザイン→Ajax検索でインクリメンタルサーチを実装→get_search_formフィルターで入力フォームを改善」の流れで整備します。関連記事:WordPressにXMLサイトマップを設定してSEOを改善する方法、WordPressのページネーションをカスタマイズする方法。