2026年5月30日

2026年5月30日

WordPressのquery_varsフィルターとwp()でカスタムURLクエリを処理する方法

?event_year=2026 のような独自のURLパラメーターをWordPressに認識させるには、query_vars フィルターでクエリ変数を登録し、get_query_var() で値を取り出します。この記事では、WordPressがURLを解釈する流れ(wp() の中身)を押さえたうえで、クエリ変数の登録、きれいなURLへの対応、メインクエリへの反映、独自出力までを順に実装できるようにします。

なお、タイトルにある「wp_query_vars()」という名前のコア関数はありません。実体は query_vars というフィルターフックで、wp() 関数が内部で呼び出す WP::parse_request() の中で適用されます。

WordPressがURLを解釈する流れ

テーマの index.php が読み込まれる前に、wp-blog-header.php から wp() 関数が呼ばれます。wp() はグローバルの $wp(WP クラス)の main() を実行し、おおむね次の順で処理します。

  1. parse_request():リライトルールとURLを照合し、許可されたクエリ変数だけを $wp->query_vars に取り込む。最後に parse_request アクションが発火する
  2. send_headers():HTTPヘッダーを送る
  3. query_posts():request フィルターを通したクエリ変数でメインの WP_Query を実行する
  4. handle_404():該当がなければ404にする
  5. 最後に wp アクションが発火する

ここで大事なのは「許可されたクエリ変数だけ」という点です。WordPressは $_GET の中身を何でも受け入れるわけではなく、公開クエリ変数のリスト($wp->public_query_vars)にある名前だけを拾います。このリストを広げる入口が query_vars フィルターです。登録していないパラメーターは $_GET には残りますが、get_query_var() では空文字が返ります。「URLに付けたのに値が取れない」というつまずきの多くはこれが原因です。

手順1:query_varsフィルターでクエリ変数を登録する

作業前にバックアップを取り、ステージング環境で試してください。コードは子テーマの functions.php か、テーマを変えても残したい場合は mu-plugin(wp-content/mu-plugins/ に置くPHPファイル)に書きます。

<?php
// クエリ変数 event_year と event_slug を公開クエリ変数に追加する
add_filter( 'query_vars', function ( $vars ) {
    $vars[] = 'event_year';
    $vars[] = 'event_slug';
    return $vars;
} );

名前には接頭辞を付けるのが安全です。year・name・page・p・m・author・order・type などはWordPress自身が使う予約語で、同じ名前を独自用途に使うと、意図しないアーカイブ判定や404の原因になります。event_ のように用途が分かる接頭辞を付けておけば衝突を避けられます。

登録後は https://example.com/?event_year=2026 のようなURLで、テンプレートやフックから値を読めます。

<?php
$year = absint( get_query_var( 'event_year' ) ); // 数値として扱う

get_query_var() はメインクエリ($wp_query)の値を読むため、使えるのは parse_query・pre_get_posts 以降のタイミングです。それより前の parse_request アクションの中では、引数で渡される $wp の $wp->query_vars['event_year'] を参照します。

手順2:値はサニタイズしてから使う

クエリ変数はURLから来る外部入力です。登録したからといって安全になるわけではありません。用途に合わせて型を絞ります。

  • 数値なら absint() や (int)、さらに範囲(例:2000〜2100)を確認する
  • スラッグ的な文字列なら sanitize_title() や sanitize_key()
  • 決まった選択肢なら、許可リストと照合して一致したものだけ使う
  • 画面に表示するときは esc_html()、属性なら esc_attr()、URLなら esc_url() で出力時にエスケープする

URLを組み立てるときは文字列連結ではなく add_query_arg() を使い、出力時に esc_url() を通します。

<?php
$url = add_query_arg( 'event_year', 2026, home_url( '/' ) );
echo '<a href="' . esc_url( $url ) . '">2026年のイベント</a>';

手順3:きれいなURL(/events/2026/)に対応させる

?event_year=2026 ではなく /events/2026/ の形にしたい場合は、リライトルールを追加します。add_rewrite_tag() はタグを登録すると同時にクエリ変数も追加するため、手順1のフィルターと併用しても問題ありません。

<?php
add_action( 'init', function () {
    add_rewrite_tag( '%event_year%', '([0-9]{4})' );
    // /events/2026/ を「固定ページ events」+ event_year=2026 として解釈する
    add_rewrite_rule(
        '^events/([0-9]{4})/?$',
        'index.php?pagename=events&event_year=$matches[1]',
        'top'
    );
} );

書き換え先に pagename=events を含めているのは、メインクエリに「どのページを表示するか」を伝えるためです。独自変数だけを渡すと、WordPressは表示対象を判断できずトップページ扱いになることがあります。この例では、あらかじめスラッグ events の固定ページを作っておきます。

リライトルールは追加しただけでは反映されません。管理画面の「設定」→「パーマリンク」を開いて何も変えずに保存すると、ルールが再生成されます。プラグインとして配布する場合は、有効化時に一度だけ flush_rewrite_rules() を呼びます。ページ表示のたびに呼ぶと重くなるため、init の中で毎回実行するのは避けてください。

反映を確かめるには、WP-CLIが使える環境なら wp rewrite list で追加したルールが一覧に出るかを見ます。Query Monitor プラグインを入れると、表示中のリクエストでどのルールに一致し、どのクエリ変数が入ったかを画面上で確認できます。

手順4:メインクエリに反映する(pre_get_posts)

クエリ変数で一覧の絞り込みを変えたいときは、テンプレート内で新しい WP_Query を作るより、pre_get_posts でメインクエリそのものを変更するほうが、ページ送りや404判定が自然に動きます。

<?php
add_action( 'pre_get_posts', function ( $query ) {
    if ( is_admin() || ! $query->is_main_query() ) {
        return; // 管理画面とサブクエリには触れない
    }
    $year = absint( $query->get( 'event_year' ) );
    if ( $year >= 2000 && $year <= 2100 && $query->is_post_type_archive( 'event' ) ) {
        $query->set( 'year', $year );
    }
} );

この例は、カスタム投稿タイプ event のアーカイブ(/event/?event_year=2026 のようなURL)を年で絞り込む想定です。手順3の固定ページ方式とは別の使い方で、固定ページ側ではテンプレート内で get_query_var( 'event_year' ) を読み、サブクエリを組み立てます。

is_admin() と is_main_query() の確認を省くと、管理画面の投稿一覧やウィジェットのサブクエリまで書き換わり、原因の分かりにくい不具合になります。条件はできるだけ狭く書くのがコツです。

手順5:独自の出力を返す(template_redirect / template_include)

クエリ変数に応じてJSONや専用ページを返したい場合は、メインクエリが確定した後の template_redirect アクションで分岐します。

<?php
add_action( 'template_redirect', function () {
    $slug = sanitize_title( get_query_var( 'event_slug' ) );
    if ( '' === $slug ) {
        return;
    }
    $event = get_page_by_path( $slug, OBJECT, 'event' );
    if ( ! $event || 'publish' !== get_post_status( $event ) ) {
        global $wp_query;
        $wp_query->set_404();
        status_header( 404 );
        return;
    }
    wp_send_json( array(
        'title' => get_the_title( $event ),
        'url'   => get_permalink( $event ),
    ) );
} );

ポイントは、見つからない場合に正しく404を返すことと、非公開の投稿を外部に出さないことです。テーマの見た目のまま専用テンプレートに差し替えたいだけなら、template_include フィルターでテンプレートファイルのパスを返す方法のほうが向いています。

つまずきやすい点と確認方法

  • 値が空になる:query_vars フィルターでの登録漏れか、get_query_var() を呼ぶタイミングが早すぎる可能性があります。parse_request 内なら $wp->query_vars を見ます。
  • /events/2026/ が404になる:パーマリンク設定の再保存を忘れていることが最も多い原因です。次に、正規表現の誤りや、より優先されるルールとの競合を疑います。
  • 予期しないアーカイブが表示される:予約語と同じ名前の変数を使っていないか確認します。
  • キャッシュで古い結果が出る:ページキャッシュ系のプラグインやサーバーキャッシュが、クエリ文字列付きURLをキャッシュ対象から外す設定かどうかを確認します。挙動は製品ごとに異なります。

リライトAPIやクエリ変数の仕組みは長く安定していますが、内部処理の細部はバージョンで変わることがあります(2026年10月現在)。実装時はWordPress公式の開発者リファレンス(developer.wordpress.org)で各関数の説明を確認してください。

まとめ

  • 「wp_query_vars()」という関数はなく、独自パラメーターは query_vars フィルターで登録する
  • wp() は parse_request() → query_posts() → handle_404() の順にURLを解釈し、登録済みの変数だけを扱う
  • 値は外部入力として absint()・sanitize_title() などで絞り、出力時にエスケープする
  • きれいなURLは add_rewrite_tag() と add_rewrite_rule() で作り、パーマリンク設定の再保存で反映する
  • 絞り込みは pre_get_posts、独自出力は template_redirect や template_include で行う

お気軽にご相談ください

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