2026年9月6日

2026年9月6日

WordPressの検索機能エラーを解決する方法

はじめに

WordPressの検索フォームで検索すると結果が0件になる・検索するとホームページにリダイレクトされてしまう・カスタム投稿タイプが検索結果に出てこない・search.phpテンプレートが適用されずindex.phpが表示される・日本語の検索が正しく機能しないといった問題は、WordPressの検索処理の仕組みを正しく理解することで解決できます。

症状・原因

  • パーマリンク設定が「プレーン」になっており?s=クエリが正しく処理されない
  • カスタム投稿タイプ登録時にexclude_from_searchtrueに設定している
  • pre_get_postsフックで検索クエリを誤って変更している
  • テーマのsearch.phpが存在しないか、テンプレート階層が正しく解決されていない

解決手順

ステップ1:検索機能の状態を診断する

# 検索クエリが正しく動作するか確認
wp eval "
\$q = new WP_Query(['s' => 'テスト', 'post_type' => 'any']);
echo '検索結果: ' . \$q->found_posts . '件' . PHP_EOL;
echo 'SQL: ' . \$q->request . PHP_EOL;
"

# カスタム投稿タイプの exclude_from_search 設定を確認
wp eval "
global \$wp_post_types;
foreach (\$wp_post_types as \$slug => \$type) {
    if (!\$type->exclude_from_search) {
        echo \$slug . ': 検索に含まれる' . PHP_EOL;
    }
}
"

# search.php テンプレートの存在確認
wp eval "echo locate_template(['search.php']);"

# パーマリンク設定を確認
wp option get permalink_structure
// 検索クエリのデバッグ
add_action('pre_get_posts', function(WP_Query $query): void {
    if ($query->is_search()) {
        error_log('Search query: ' . $query->get('s'));
        error_log('Post types: ' . print_r($query->get('post_type'), true));
        error_log('Is main query: ' . ($query->is_main_query() ? 'yes' : 'no'));
    }
});

ステップ2:検索クエリを拡張する

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

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

    // 検索結果の件数を設定
    $query->set('posts_per_page', 20);

    // カスタムフィールドも検索対象に含める
    $query->set('meta_query', [
        'relation' => 'OR',
        // 通常の検索(デフォルト)に加えてメタも検索
    ]);
});

// カスタムフィールドも検索対象に含める(posts_search フィルター)
add_filter('posts_search', function(string $search, WP_Query $query): string {
    if (!$query->is_search() || !$query->is_main_query()) {
        return $search;
    }

    global $wpdb;
    $term = $query->get('s');
    if (empty($term)) {
        return $search;
    }

    $like = '%' . $wpdb->esc_like($term) . '%';
    $search .= $wpdb->prepare(
        " OR EXISTS (
            SELECT 1 FROM {$wpdb->postmeta}
            WHERE {$wpdb->postmeta}.post_id = {$wpdb->posts}.ID
            AND {$wpdb->postmeta}.meta_key NOT LIKE '\\_%%'
            AND {$wpdb->postmeta}.meta_value LIKE %s
        )",
        $like
    );

    return $search;
}, 10, 2);

ステップ3:検索テンプレートをカスタマイズする

// search.php(テーマに配置)
// テンプレート階層: search.php → index.php

// 検索フォームのカスタマイズ(searchform.php)
// テーマの searchform.php が優先される
function my_custom_searchform(string $form): string {
    return '<form role="search" method="get" action="' . esc_url(home_url('/')) . '">
        <label for="search-field" class="screen-reader-text">検索</label>
        <input type="search" id="search-field" name="s"
               value="' . get_search_query() . '"
               placeholder="キーワードを入力..."
               aria-label="サイト内検索">
        <select name="post_type">
            <option value="">すべて</option>
            <option value="post" ' . selected(get_query_var('post_type'), 'post', false) . '>記事</option>
            <option value="product" ' . selected(get_query_var('post_type'), 'product', false) . '>商品</option>
        </select>
        <button type="submit">検索</button>
    </form>';
}
add_filter('get_search_form', 'my_custom_searchform');

// 検索結果ページのタイトル
add_filter('document_title_parts', function(array $title): array {
    if (is_search()) {
        $title['title'] = sprintf('「%s」の検索結果', get_search_query());
    }
    return $title;
});

ステップ4:日本語検索を改善する

// 日本語の全文検索を改善(形態素解析なしでの工夫)
add_filter('posts_search', function(string $search, WP_Query $query): string {
    if (!$query->is_search()) {
        return $search;
    }

    $term = $query->get('s');
    // 日本語の場合はスペースで分割して各語を AND 検索
    $terms = preg_split('/[\s ]+/u', trim($term), -1, PREG_SPLIT_NO_EMPTY);

    if (count($terms) <= 1) {
        return $search;
    }

    global $wpdb;
    $conditions = [];
    foreach ($terms as $t) {
        $like = '%' . $wpdb->esc_like($t) . '%';
        $conditions[] = $wpdb->prepare(
            "({$wpdb->posts}.post_title LIKE %s OR {$wpdb->posts}.post_content LIKE %s)",
            $like,
            $like
        );
    }

    return ' AND (' . implode(' AND ', $conditions) . ')';
}, 10, 2);

// 検索ハイライトを追加
add_filter('the_excerpt', function(string $excerpt): string {
    if (!is_search()) {
        return $excerpt;
    }
    $term = get_search_query();
    if (empty($term)) {
        return $excerpt;
    }
    $pattern = '/' . preg_quote($term, '/') . '/ui';
    return preg_replace($pattern, '<mark>$0</mark>', $excerpt);
});

ステップ5:検索をREST APIと連携させる

// REST API で検索を提供(フロントエンドSPA対応)
add_action('rest_api_init', function(): void {
    register_rest_route('myplugin/v1', '/search', [
        'methods'             => WP_REST_Server::READABLE,
        'callback'            => function(WP_REST_Request $request): WP_REST_Response {
            $term      = sanitize_text_field($request->get_param('q') ?? '');
            $post_type = sanitize_key($request->get_param('type') ?? 'any');

            if (empty($term)) {
                return new WP_REST_Response(['results' => [], 'total' => 0], 200);
            }

            $query = new WP_Query([
                's'              => $term,
                'post_type'      => $post_type,
                'posts_per_page' => 10,
                'post_status'    => 'publish',
                'no_found_rows'  => false,
            ]);

            $results = array_map(fn($p) => [
                'id'      => $p->ID,
                'title'   => $p->post_title,
                'excerpt' => wp_trim_words($p->post_excerpt ?: $p->post_content, 20),
                'url'     => get_permalink($p->ID),
                'type'    => $p->post_type,
            ], $query->posts);

            return new WP_REST_Response([
                'results' => $results,
                'total'   => $query->found_posts,
            ], 200);
        },
        'permission_callback' => '__return_true',
        'args'                => [
            'q'    => ['required' => true, 'sanitize_callback' => 'sanitize_text_field'],
            'type' => ['default' => 'any', 'sanitize_callback' => 'sanitize_key'],
        ],
    ]);
});

注意事項

  • posts_searchフィルターでカスタムフィールドを検索対象に追加すると、テーブルJOINが増えてクエリが重くなります。投稿数が多いサイトでは、Elasticsearch(ElasticPressプラグイン)や専用の検索インデックステーブルの導入を検討してください
  • pre_get_postsフックは管理画面のクエリにも影響します。必ずis_admin()チェックとis_main_query()チェックを組み合わせて、フロントエンドのメインクエリのみに適用してください

まとめ

WordPress検索問題の解決は①WP_Query(['s'=>'テスト'])で検索SQLを確認・$wp_post_typesでexclude_from_search設定を確認・pre_get_postsフックでデバッグログ出力、②pre_get_postsでpost_typeに検索対象を追加・posts_searchフィルターでカスタムフィールドもLIKE検索・posts_per_pageで件数を設定、③search.phpテンプレートを作成・get_search_formフィルターで独自フォームを実装・post_type選択UIで絞り込み機能を追加、④日本語スペース区切りでAND検索・the_excerptフィルターで検索語をハイライト表示・全角スペース対応の正規表現、⑤REST APIで/myplugin/v1/searchエンドポイントを作成・フロントエンドSPA向けにJSON形式で検索結果を返却の手順で解決します。

お気軽にご相談ください

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