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() を実行し、おおむね次の順で処理します。
parse_request():リライトルールとURLを照合し、許可されたクエリ変数だけを$wp->query_varsに取り込む。最後にparse_requestアクションが発火するsend_headers():HTTPヘッダーを送るquery_posts():requestフィルターを通したクエリ変数でメインのWP_Queryを実行するhandle_404():該当がなければ404にする- 最後に
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で行う