2026年5月20日
2026年5月20日
WordPressの検索が思いどおりに動かないときの原因と直し方|仕組み・全角スペース・カスタムフィールド
WordPressのサイト内検索で「あるはずの記事が出ない」「関係ない記事が出る」と感じるときは、多くの場合、標準の検索の仕組みを知ると原因が分かります。標準の検索は、データベースに保存された文字列の部分一致で探すだけで、日本語の単語の区切りや、カスタムフィールドの中身までは見ていません。
この記事では、標準の検索の仕組みを確認したうえで、症状ごとの直し方を説明します。仕組みの説明は、2026年10月時点のWordPress 6.8系のソースコードで確認しています。コードはテーマの functions.php か、自作のプラグインに追加します。
標準の検索の仕組み
サイトの検索フォームから ?s=検索語 で送られた検索は、WordPressの WP_Query が次のように処理します。
| 項目 | 標準の動き |
|---|---|
| 探す場所 | 投稿の題名(post_title)、抜粋(post_excerpt)、本文(post_content) |
| 一致の方法 | SQLの LIKE による部分一致。本文はHTMLやブロックのコメントを含んだ保存時の文字列のまま探す |
| 語の区切り | 半角スペース、タブ、カンマ、+。全角スペースは区切りにならない |
| 複数の語 | すべての語を含む投稿だけが対象(AND) |
| 特別な書き方 | "..." で囲むと語句のまま探す。語の先頭に - を付けるとその語を含む投稿を除く |
| 対象の投稿タイプ | exclude_from_search が false のすべての投稿タイプ |
| 並び順 | 並び順の指定が無ければ関連度順。題名に語句がそのまま含まれるもの、題名にすべての語を含むもの、題名にいずれかの語を含むもの、抜粋、本文の順に上位になり、同じ順位の中では新しい順 |
ここから、次のような症状の理由が分かります。
- カスタムフィールド、カテゴリー名、タグ名は探す場所に入っていないため、そこにしか無い語ではヒットしない
- ショートコードやブロックが表示時に作る文字は保存されていないため、ヒットしない
- 本文の保存時の文字列には
<!-- wp:paragraph -->のようなブロックのコメントやHTMLのクラス名が含まれるため、wpやclassのような短い英語で探すと、関係の無い投稿までヒットする - 「WordPress テーマ」のように全角スペースで区切ると、全体が1つの語として扱われ、その並びをそのまま含む投稿しかヒットしない
まず、検索が動いているかを確かめる
ブラウザで https://サイトのURL/?s=検索語 を直接開きます。ここで結果が出るのに、検索フォームから探すと結果が変わる場合は、テーマの検索フォームが原因です。フォームの入力欄の name が s になっているか、送信先が home_url( '/' ) になっているかを確認します。WordPress標準のフォームを出す get_search_form() を使うのが簡単です。
SSHが使える場合は、WP-CLIで実際に組み立てられるSQLを確認できます。
wp eval '$q = new WP_Query( array( "s" => "テーマ" ) ); echo $q->found_posts, PHP_EOL, $q->request, PHP_EOL;'
表示されるSQLの WHERE に、対象の投稿タイプや探す列が出ています。テーマやプラグインが pre_get_posts で検索の条件を変えている場合は、サイトで探した結果とこの結果が変わります。その場合は、テーマとプラグインの中で pre_get_posts や is_search を使っている箇所を探します。
cd /var/www/html/wp-content
grep -rln "is_search" themes/ plugins/ mu-plugins/ 2>/dev/null
全角スペースを区切りとして扱う
日本語の入力では、語の間に全角スペースが入ることがよくあります。全角スペースを半角スペースに置き換えてから検索させると、それぞれの語を含む投稿がヒットするようになります。
<?php
// サイトの検索で、全角スペースを半角スペースに置き換える
add_action( 'pre_get_posts', function ( $query ) {
if ( is_admin() || ! $query->is_main_query() || ! $query->is_search() ) {
return;
}
$s = (string) $query->get( 's' );
$query->set( 's', trim( preg_replace( '/[ ]+/u', ' ', $s ) ) );
} );
is_main_query() と is_search() で、サイトの検索結果ページのメインの問い合わせだけに限っています。is_admin() の確認は、管理画面の投稿一覧の検索を変えないためです。
投稿タイプを検索の対象に入れる・外す
カスタム投稿タイプがヒットしない
検索の対象になるかどうかは、投稿タイプを登録するときの exclude_from_search で決まります。この値を指定しない場合は public の反対の値になるため、public を true にした投稿タイプは、通常は検索の対象です。自分で登録している投稿タイプなら、登録の引数を確認します。
<?php
add_action( 'init', function () {
register_post_type( 'product', array(
'label' => '商品',
'public' => true,
'exclude_from_search' => false, // 検索の対象にする
'has_archive' => true,
) );
} );
プラグインが登録した投稿タイプは、register_post_type_args フィルターで引数を変えられます。
<?php
// プラグインが登録した faq 投稿タイプを検索の対象にする
add_filter( 'register_post_type_args', function ( $args, $post_type ) {
if ( 'faq' === $post_type ) {
$args['exclude_from_search'] = false;
}
return $args;
}, 10, 2 );
exclude_from_search を false にすると、そのタイプの投稿はサイトの検索に出るようになります。公開したくない内容を含む投稿タイプは、対象に入れないでください。
検索の対象を特定の投稿タイプに絞る
逆に、固定ページなどを検索結果から外したいときは、pre_get_posts で対象を指定します。
<?php
// サイトの検索を、投稿と product だけにする
add_action( 'pre_get_posts', function ( $query ) {
if ( is_admin() || ! $query->is_main_query() || ! $query->is_search() ) {
return;
}
$query->set( 'post_type', array( 'post', 'product' ) );
} );
特定のカテゴリーの投稿を外す場合は、同じ場所で $query->set( 'category__not_in', array( 5, 10 ) ); のようにカテゴリーIDを指定します。
並び順が関連度順にならない
標準では、題名に検索語を含む投稿が上位に来ます。新しい順に並んでしまう場合は、テーマやプラグインが pre_get_posts で orderby を date などに変えていることが考えられます。前の節の grep で見つけた箇所を確認するか、検索のときだけ関連度順に戻します。
<?php
// サイトの検索では関連度順で並べる
add_action( 'pre_get_posts', function ( $query ) {
if ( is_admin() || ! $query->is_main_query() || ! $query->is_search() ) {
return;
}
$query->set( 'orderby', 'relevance' );
}, 20 );
優先度を20にしているのは、初期値の10で動く他のコードより後に実行するためです。
カスタムフィールドの中身も検索の対象にする
カスタムフィールドの値でも探したい場合は、posts_search フィルターで検索の条件を組み立て直します。次の例は、検索語ごとに「題名・抜粋・本文、または指定したカスタムフィールドに含まれる」という条件を作ります。
<?php
// 指定したカスタムフィールドの値も、サイトの検索の対象にする
add_filter( 'posts_search', function ( $search, $query ) {
global $wpdb;
if ( is_admin() || ! $query->is_main_query() || ! $query->is_search() || '' === $search ) {
return $search;
}
$meta_keys = array( 'product_description', 'faq_answer' ); // 対象にするカスタムフィールドのキー
$key_list = implode( ', ', array_fill( 0, count( $meta_keys ), '%s' ) );
$groups = array();
foreach ( (array) $query->get( 'search_terms' ) as $term ) {
if ( str_starts_with( $term, '-' ) ) {
continue; // 除外の指定(-語)はこの例では扱わない
}
$like = '%' . $wpdb->esc_like( $term ) . '%';
$groups[] = $wpdb->prepare(
"({$wpdb->posts}.post_title LIKE %s OR {$wpdb->posts}.post_excerpt LIKE %s OR {$wpdb->posts}.post_content LIKE %s"
. " OR {$wpdb->posts}.ID IN (SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key IN ($key_list) AND meta_value LIKE %s))",
array_merge( array( $like, $like, $like ), $meta_keys, array( $like ) )
);
}
if ( ! $groups ) {
return $search;
}
$search = ' AND (' . implode( ' AND ', $groups ) . ') ';
if ( ! is_user_logged_in() ) {
// 標準の検索と同じく、ログインしていない人にはパスワード保護の投稿を出さない
$search .= " AND ({$wpdb->posts}.post_password = '') ";
}
return $search;
}, 10, 2 );
このコードのポイントは次のとおりです。
- 検索語はWordPressが区切った
search_termsをそのまま使い、値は$wpdb->esc_like()と$wpdb->prepare()を通してからSQLに入れる - 標準の条件の後ろに
OR ...を足すだけの書き方は、SQLの優先順位の関係で、公開状態や投稿タイプの条件まで無視されて下書きなどが結果に混ざることがあるため、条件全体を組み立て直している - 除外の指定(
-語)は扱わないため、このコードを入れると-を付けた語は無視される
カスタムフィールドの数や投稿が多いサイトでは、検索のたびに postmeta テーブルを探すため、検索が遅くなることがあります。その場合は、次の節の検索プラグインを検討します。
検索プラグインを使う
標準の検索の仕組みでは足りない場合は、専用の索引を作って探すプラグインを使います。
| プラグイン | 特徴 |
|---|---|
| Relevanssi | 無料版がある。WordPressのデータベースに独自の索引を作り、関連度順に並べる。カスタムフィールドやタクソノミーも索引に入れられる |
| ElasticPress | 検索をElasticsearchに任せる。Elasticsearchのサーバー(自前で用意するか、有料のホスティングを使う)が別に必要で、投稿の多い大規模サイト向け |
Relevanssiを使う場合は、有効にしたあと、設定画面の「Indexing」タブで索引の対象(投稿タイプ、カスタムフィールドなど)を選び、索引を作成します。日本語は単語の間に空白が無い言語のため、導入する前に、実際の記事と、よく探される語で結果を試してから切り替えると安心です。
ElasticPressとElasticsearchの組み合わせは、WordPressでElasticsearchを使ったサイト内検索で説明しています。
まとめ
- 標準の検索は、題名・抜粋・本文の部分一致で、カスタムフィールドやタグ名は探さない。本文はHTMLを含んだ保存時の文字列のまま探す
?s=検索語を直接開いて、フォームの問題か検索の問題かを分け、wp evalでSQLを確かめる- 全角スペースは区切りにならないため、
pre_get_postsで半角に置き換える - 投稿タイプは
exclude_from_searchで決まる。プラグインの投稿タイプはregister_post_type_argsで変える - 並び順は
orderbyをrelevanceに戻す。カスタムフィールドはposts_searchで条件を組み立て直すか、Relevanssiなどの検索プラグインを使う