今ある一覧を変えるならメイン、別の一覧を足すなら独自WP_Query
カテゴリー・検索・投稿一覧など、WordPressがそのURLのために用意した一覧条件を変えたいなら、メインクエリを対象にします。記事下の関連記事やサイドバーの新着記事など、今のページへ別の一覧を追加したいなら、new WP_Query()などで独自のクエリを作ります。まず「変える」と「追加する」を分けると、メインクエリとサブクエリの違いを実装へ結びつけられます。
最初に決めるのは、今ある一覧を変えるか、別の一覧を追加するか
「メインクエリとは何ですか」と調べても、説明だけでは自分のコードに当てはめにくいものです。先に、画面でやりたいことを1文にします。
| やりたいことの例 | どちらに当たるか |
|---|---|
| カテゴリー一覧を1ページ6件に変える | 今ある一覧を変える |
| 検索結果から固定ページを除外する | 今ある一覧を変える |
| 記事本文の下へ関連記事を3件追加する | 別の一覧を追加する |
| サイドバーへ新着記事を5件追加する | 別の一覧を追加する |
最初の2つは、そのURLにもともとある一覧の条件変更です。後の2つは、現在のページに別の一覧を足す処理です。この違いが、実装方法を選ぶ入口になります。
| やりたいこと | 対象 | 基本の選択 |
|---|---|---|
| カテゴリー・タグ・検索・投稿一覧の条件を変える | WordPressが用意したメインクエリ | pre_get_postsで対象を絞って変更する |
| 記事下・サイドバー・固定ページ内へ別の一覧を足す | テンプレート内の独自クエリ | new WP_Query()または用途に応じてget_posts() |
| ブロックテーマで追加する一覧を作る | Query Loopブロック | まずエディターで設定できるか確認する |
| 古いquery_posts()を書き換える | 置き換え前の役割を確認 | メイン変更か追加する一覧かで移行先を分ける |
用語よりページと目的を組み合わせて調べる
まだ方法が決まっていないときは、関数名やメソッド名だけで調べるより、WordPressの「どのようなページ」で「何をしたいか」「何を変えたいか」を組み合わせて検索するところから始めましょう。最初からWP_Queryだけに絞ると、テーマの設定やブロックで済む方法を見落とすこともあります。
| やりたいこと | 最初の検索語 | 用語が分かった後 |
|---|---|---|
| カテゴリー一覧の表示件数を変えたい | WordPress カテゴリー一覧 表示件数 変更 | WordPress pre_get_posts posts_per_page |
| 検索結果の対象を変えたい | WordPress 検索結果 固定ページ 除外 | pre_get_posts is_search post_type |
| 記事の下に関連記事を足したい | WordPress 記事下 関連記事 追加 | WP_Query 関連記事 category__in |
| サイドバーに新着を足したい | WordPress サイドバー 新着記事 追加 | WP_Query 新着 posts_per_page |
表の左側に近い目的があれば、まずはその言葉で調べます。使えそうな方法が見つかったら、次の順番で自分のサイトにも合うか確かめてください。
調べる順番
- 1ページを特定する:カテゴリー、検索、投稿詳細、固定ページ、サイドバーなど
- 2目的を動詞で書く:変える、除外する、追加する、並べるなど
- 3今ある一覧の変更か、別の一覧の追加かを分ける
- 4使う方法が見えたら関数名やパラメーター名を検索語へ足す
- 5公式リファレンスで対象範囲と注意点を確認する
- 6ローカルまたはステージングで0件・1件・複数件を試す
この順番なら、「メインクエリとサブクエリの違い」という抽象的な検索から、実際のページと目的へ戻れます。逆に、実装例だけを先に貼ると、そのコードが現在の一覧を変えるものか、一覧を追加するものか分からないままになりがちです。
メインクエリは、そのURLに必要な投稿を選ぶ処理
WordPressはURLを受け取ると、どの種類のページかを判断し、そのページに必要な投稿を取得します。カテゴリーURLならそのカテゴリーの投稿、検索URLなら検索条件に合う投稿、個別記事URLなら該当する1件です。開いているURLに必要な投稿を選ぶ、この中心の処理がメインクエリです。
メインクエリは「投稿一覧専用」ではありません。個別記事や固定ページでも、現在表示する投稿を決めるメインクエリがあります。テンプレートは、その結果とページ判定を使って表示を組み立てます。
この記事でいうサブクエリは、追加する一覧のための独自クエリ
記事下の関連記事、サイドバーの新着、固定ページ内の商品一覧など、WordPressがそのページのために用意した投稿とは別の記事を取り出したい時は、独自のクエリを作ります。この「別の一覧を追加するための投稿取得」が、この記事でいうサブクエリです。 WordPress公式ではcustom queryやsecondary queryと表現され、日本語のテーマ制作では「サブクエリ」と呼ばれることがあります。ここでは「追加クエリ」とも呼びます。
なお、SQLについて調べると、別のSELECT文を中に入れる書き方も「サブクエリ」と呼ばれます。ここではその書き方ではなく、WordPressのメインクエリとは別に投稿を取り出す話をしています。

クエリは投稿を選ぶ条件と取得結果、ループはその結果を1件ずつ表示する処理です。追加ループの後は、global $postが指す記事をメインループ側へ戻します。
クエリとループは同じものではない
混乱しやすいのは、「メインクエリ」と「メインループ」、「追加クエリ」と「追加ループ」が一緒に説明されることです。クエリは、どの投稿をどんな条件で取得するかと、その結果を持つオブジェクトです。ループは、取得結果を1件ずつ進め、タイトルや本文を出力する処理です。
| 言葉 | 役割 | 代表的なもの |
|---|---|---|
| メインクエリ | 現在のURLに必要な取得条件と結果 | global $wp_query / is_main_query() |
| メインループ | メインクエリの結果を1件ずつ表示 | have_posts() / the_post() |
| 独自・追加クエリ | テンプレート内で追加した取得条件と結果 | $latest_query = new WP_Query($args) |
| 追加ループ | 独自クエリの結果を1件ずつ表示 | $latest_query->have_posts() / $latest_query->the_post() |
つまり、メインクエリを変えることと、メインループのHTMLを変えることも別です。表示件数や対象投稿を変えるのはクエリ側、カードのタグや見出しを変えるのは主にテンプレート側です。
メインループは、新しいWP_Queryを作らずに結果を表示する
カテゴリーや検索のテンプレートでは、WordPressがすでにメインクエリを実行しています。メインループでは、その結果をそのまま使います。ここからのPHP例はクラシックテーマ向けです。ブロックテーマでは、まずQuery Loopブロックの設定を確認してください。
次のコードは、子テーマのarchive.phpにある既存のメインループ部分と置き換えます。ファイル全体を上書きせず、ヘッダー・フッターや既存のページ送りは残してください。category.phpなど、より優先されるテンプレートがある場合は、実際に読み込まれるファイルのループを対象にします。検索結果ならsearch.phpです。
クラシックテーマのテンプレート(またはテンプレートパーツ)で、一覧を表示したい位置に置く例です。投稿本文やfunctions.phpへ、そのまま貼るコードではありません。
<?php if ( have_posts() ) : ?> <ul class="article-list"> <?php while ( have_posts() ) : ?> <?php the_post(); ?> <li> <a href="<?php the_permalink(); ?>"> <?php the_title(); ?> </a> </li> <?php endwhile; ?> </ul><?php else : ?> <p>該当する記事はありません。</p><?php endif; ?>ここにはnew WP_Query()がありません。カテゴリーや検索など、現在のURLに合う取得条件はWordPressが準備済みだからです。HTMLを調整するだけなら、メインクエリを作り直す必要もありません。
テンプレートの変更は、本番と分けたWordPress環境で表示を確認します。検証用や公開用のサーバーをこれから用意する場合は、XserverのPHP対応やバックアップ条件も確認できます。
追加する一覧では、専用の変数でWP_Queryとループを動かす
サイドバーへ新着記事を追加する場合は、メインとは別の変数を用意します。次の例の目的は、サイドバーへ、公開済みの通常投稿を新しい順に最大3件表示することです。先頭固定の記事を優先せず、ページ送りや総件数の表示も行いません。
子テーマにtemplate-parts/latest-posts.phpを作り、次のコード全体を保存します。続いて、読み込まれているsidebar.phpの新着一覧を出したい位置へ、<?php get_template_part( ‘template-parts/latest-posts’ ); ?>を1回だけ追加してください。パーツの保存と、サイドバーからの呼び出しの両方が必要です。
すでにPHPのコードを書いている場所なら、<?phpと?>は不要です。get_template_part( ‘template-parts/latest-posts’ );だけを書きます。今回のコードは一覧を画面に表示するためのものなので、functions.phpには貼り付けません。
クラシックテーマのテンプレート(またはテンプレートパーツ)で、一覧を表示したい位置に置く例です。投稿本文やfunctions.phpへ、そのまま貼るコードではありません。
<?php$latest_args = [ 'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => 3, 'orderby' => 'date', 'order' => 'DESC', 'ignore_sticky_posts' => true, 'no_found_rows' => true,]; $latest_query = new WP_Query( $latest_args );?> <?php if ( $latest_query->have_posts() ) : ?> <aside class="latest-posts" aria-labelledby="latest-posts-title"> <h2 id="latest-posts-title">新着記事</h2> <ul> <?php while ( $latest_query->have_posts() ) : ?> <?php $latest_query->the_post(); ?> <li> <a href="<?php the_permalink(); ?>"> <?php the_title(); ?> </a> </li> <?php endwhile; ?> </ul> </aside><?php endif; ?> <?php wp_reset_postdata(); ?>$latest_queryのhave_posts()とthe_post()を使っているため、どのクエリを進めているかが分かります。0件なら新着一覧の枠を出しません。ignore_sticky_postsは先頭固定の優先を止め、no_found_rowsは今回は使わない総件数の計算を省きます。
追加ループのthe_post()はglobal $postが指す記事を一時的に切り替えるため、終わったらwp_reset_postdata()でメインループ側へ戻します。これは、メインループで投稿を準備した後にサイドバーを呼ぶ配置を想定しています。別の独自ループの内側から呼ぶ場合は、その外側のクエリへ戻す処理を別に考えてください。リセットが必要な理由は、wp_reset_postdata()が必要な理由で詳しく説明しています。
一覧の追加に専用のWP_Queryを使うと決めたら、その取得条件をWP Query Builderで整理できます。メインの一覧を変えるpre_get_postsの代わりではありません。
追加する一覧の条件を組み立てる
投稿タイプ・絞り込み・件数を$argsにし、専用変数のループへ渡します。
メイン一覧の条件変更は、表示前に対象を絞って行う
カテゴリー一覧の件数を変えるために、テンプレート内で同じカテゴリーのnew WP_Query()を作ると、WordPressが実行したメインクエリに加えて、似た投稿取得をもう一度実行することになります。ページネーションやページ判定も別管理になり、必要以上に複雑です。
メインクエリの条件変更には、通常pre_get_postsを使います。ただし、このフックには管理画面や追加クエリも通るため、変更対象を明示します。
次のコードは、子テーマのfunctions.phpのPHP内へ1回だけ追加します。既存の関数やコールバックの内側へ入れず、別の処理として登録してください。archive.phpの表示コードへ貼ると、そのリクエストのメインクエリには間に合いません。変更前のファイルを残し、まず本番と分けた環境で試します。
add_action( 'pre_get_posts', function ( WP_Query $query ) { if ( is_admin() || ! $query->is_main_query() ) { return; } if ( $query->is_category() ) { $query->set( 'posts_per_page', 6 ); } });$query->is_main_query()でメインクエリだけに絞り、$query->is_category()でカテゴリー一覧だけに絞っています。フック内では、グローバル関数のis_main_query()ではなく、渡された$queryのメソッドを使います。条件を付けずに変更すると、サイドバーやフッターの追加クエリまで変える可能性があります。
選び方を間違えると起きやすいこと
選択肢の違いと、最初に見る点を次の表で整理します。
| 状態 | 起きやすい問題 | 見直す方向 |
|---|---|---|
| アーカイブで同じ一覧をnew WP_Query()し直す | 同じような投稿取得が増え、ページ送りと条件を別々に管理することになる | メインクエリをpre_get_postsで変更できないか確認 |
| 追加する一覧のために条件なしでpre_get_postsを使う | メインだけでなく他のクエリまで変わる | 追加する一覧は専用のWP_Queryへ分ける |
| query_posts()でメインループを書き換える | 元のメインクエリを置き換え、投稿取得も増える | 目的を分けてpre_get_postsまたはnew WP_Query()へ移行 |
| 追加ループ後にリセットしない | 後続のタイトルやリンクが追加する一覧の投稿を指す | wp_reset_postdata()でglobal $postを戻す |
| クエリ変数とグローバル関数を混ぜる | 判定とthe_post()が別の結果を進める | 同じ独自変数のメソッドに揃える |
古いコードのquery_posts()を見つけた場合は、そのままnew WP_Query()へ置き換えないでください。まず、そのコードが「今ある一覧を変える」のか「別の一覧を追加する」のかを確認します。移行方法は、query_postsを使わないほうがいい理由で詳しく説明しています。
新着一覧を追加すると、元の記事はどうなる?
サンプルの記事79に、先ほどの新着一覧を追加してみましょう。メインループでthe_post()を呼び、記事を表示した後に追加しています。一覧の位置が分かりやすいよう、この画面ではサイドバーではなく記事本文の下に配置しました。使う取得・表示・リセットのコードは同じです。WordPress 7.1/PHP 8.4.20で、処理中に参照する記事がどう切り替わるかを比べます。
| 確認内容 | 結果 |
|---|---|
| 記事79のメインクエリのis_main_query() | true |
| new WP_Query()で作った追加クエリのis_main_query() | false |
追加クエリを作った後のglobal $wp_query | 元のメインクエリのまま |
| メインループで準備した投稿ID | 79 |
追加ループ中のglobal $post | 82→81→80の順に切り替わる |
wp_reset_postdata()後のglobal $post | メインループの投稿ID 79へ復帰 |

記事79の本文下にある新着一覧は、掲載コードを実行した出力です。取得IDは82・81・80、リセット後は79です。末尾の確認値は検証用に添えており、メイン判定と追加判定は直前の表で確認できます。
投稿IDはサンプルの値なので、別のサイトでは変わります。新着一覧を追加しても、メインクエリそのものは置き換わりません。 ただし、追加ループ中はglobal $postが新着一覧の記事を指すため、最後にリセットして元の記事へ戻します。
候補を0件・1件・2件に絞った場合も、0件なら枠を出さず、1件・2件ならその数だけ表示されます。いずれもリセット後は記事79へ戻ります。この比較では、上の3件取得の条件に、候補を限定する条件だけを加えています。
pre_get_postsの例も、管理画面相当と公開ページ相当を別のPHPプロセスで試しました。カテゴリーのメインだけが2件指定から6件指定へ変わり、追加クエリ・検索・管理画面相当では2件のままでした。検証データのカテゴリー10には3記事しかないため、6件指定でも実際の表示は3件です。該当記事がないメインループでは「該当する記事はありません。」が出ました。
WP Query Builderは追加する一覧の条件整理に使う
記事下や固定ページ内へ追加する一覧を作る場合は、取得条件をWP Query Builderで組み立てられます。まずPost Type、Post Status、posts_per_pageの最小条件を作り、生成された$argsを独自のWP_Queryへ渡します。
WP Query Builderが作るのは取得条件の$argsです。メインクエリを書き換えるpre_get_postsのフックや、テンプレート内の出力HTMLまでは自動で決まりません。生成コードを置く場所に迷う場合は、WP_Queryはどこに書く?を確認してください。
公開中の画面で、通常投稿・公開済み・3件・日付降順を指定し、自動高速化をオフ、no_found_rowsとignore_sticky_postsをtrueにした未編集の出力が次です。先ほどの新着一覧と、パラメーターの名前・値が一致します。表示ループを含む完成コードは手書きであり、この出力そのものではありません。
$args = array( 'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => 3, 'order' => 'DESC', 'orderby' => 'date', 'no_found_rows' => true, 'ignore_sticky_posts' => true);この配列だけでは一覧は表示されません。先ほどの完成コードと組み合わせるなら、$latest_argsの配列定義をこの出力へ置き換え、生成部分に限って変数名を$latest_argsへ変更します。元の配列と2つ並べず、その後の専用クエリ・ループ・リセットは残してください。
実装前に6つ答えれば、対象を選びやすい
うまく動かないときは、次の項目を上から見直します。
メイン変更か追加する一覧かの判断
- 変更したいページのURL種類を言える:カテゴリー、検索、個別記事、固定ページなど
- そのページにすでにWordPressの一覧があるか確認した
- 目的を「変える」か「追加する」で言い換えた
- 取得条件の変更と、カードHTMLなど見た目の変更を分けた
- メイン変更なら管理画面と追加クエリを除外する条件を考えた
- 追加する一覧なら専用変数、0件表示、wp_reset_postdata()を含めた
古いquery_posts()を使ったコードが残っている場合は、置き換える理由と移行先も確認しておきましょう。
is_main_query()と複数ループの公式資料
is_main_query()の判定対象、pre_get_postsで条件を変える時点、複数ループで投稿データを戻す流れは、次の公式資料で確認できます。
WordPress公式
確認に使った公式ドキュメント
メインクエリとサブクエリのよくある質問
実装時によく出る疑問を、短い回答と一緒に確認します。
WP_Queryはすべてサブクエリですか?
いいえ。WordPress本体もメインクエリをWP_Queryオブジェクトとして扱います。テーマ内でnew WP_Query()を使って追加するものは通常、独自クエリまたはsecondary queryです。
メインループとは何ですか?
WordPressが現在のURLのために取得したメインクエリの結果を、have_posts()とthe_post()で1件ずつ表示する処理です。メインクエリという取得と、メインループという表示処理は分けて考えます。
アーカイブテンプレート内でnew WP_Query()を使ってもよいですか?
関連記事など別の一覧を追加する目的なら使えます。元のアーカイブ一覧の件数や対象を変えるために同じ取得を作り直すなら、pre_get_postsでメインクエリを変えられないか先に確認します。
追加ループ後にwp_reset_postdata()が必要なのはなぜですか?
独自クエリのthe_post()がglobal $postの参照先を一時的に変えるためです。その後のthe_title()やthe_permalink()がメインループ側の記事を参照できるように戻します。
pre_get_postsはメインクエリだけに効きますか?
いいえ。フック自体には複数のクエリが通ります。公開ページのメインだけを変えたい場合は、is_admin()で管理画面リクエストを除外し、渡された$query->is_main_query()などで対象を絞ります。
この記事の検証情報
検証環境
- WordPress
- 7.1.4
- PHP
- 8.3.36
- テーマ
- wpq-media + DDEV
- 対象
- カスタム投稿 / taxonomy / meta_query
更新履歴
- 2026年10月17日 初回公開
この記事を活用する
あとで見返す・共有する
お気に入りに保存できます。役に立ったポイントは、いいねや共有で教えてください。



