2026年8月30日

2026年8月30日

get_postsとquery_postsの違い|WP_Query・pre_get_postsとの使い分け

WordPress で投稿の一覧を取り出す関数には get_posts()・query_posts()・WP_Query があり、メインクエリを変える方法として pre_get_posts フックもあります。名前が似ていますが、役割は大きく違います。

結論から書くと、次のように使い分けます(2026年10月時点の developer.wordpress.org の記述にもとづく)。

目的 使うもの
サイドバーや記事下などに、別の投稿一覧を少し出す get_posts()
別の一覧を出し、ページ送りも付けたい WP_Query
トップページ・アーカイブなど、もともとの一覧(メインクエリ)の条件を変えたい pre_get_posts
query_posts() 使わない(既存コードは置き換える)

query_posts()を使わない理由

公式リファレンスでは、query_posts() についてメインクエリを丸ごと上書きする関数で、プラグインやテーマでの使用は想定していないと説明し、なるべく避けるよう書かれています。代わりに、メインクエリの変更には pre_get_posts、別の一覧には WP_Query か get_posts() を勧めています。

問題になる点は次のとおりです。

  • WordPress はテンプレートを読み込む前に、URL に応じたメインクエリをすでに実行しています。query_posts() はその後でもう一度クエリを実行するため、データベースへの問い合わせが無駄に増えます。
  • グローバル変数 $wp_query を置き換えるので、条件分岐タグ(is_home() など)や、ページ送り(paged)の扱いがずれることがあります。2ページ目が404になる、同じ記事が表示される、といった不具合の原因になります。
  • 使った後に wp_reset_query() を呼ばないと、後に続く処理が上書きされたクエリのまま動きます。

古いテーマに次のようなコードがあれば、後の節の方法に置き換えます。

<?php
// 置き換え対象の例(使わない)
query_posts( 'posts_per_page=5&category_name=blog' );

get_posts()の使い方

get_posts() は、条件に合う投稿を 配列 で返す関数です。中では WP_Query を使っています。グローバルなクエリは変えないので、テンプレートのどこで使ってもメインの一覧には影響しません。

既定値

公式リファレンスに載っている既定値は次のとおりです。

引数 既定値 意味
numberposts 5 取得件数。-1 で全件
category 0 カテゴリーID(cat として渡される)
orderby / order date / DESC 新しい順
include / exclude 空 投稿IDで含める/除く
post_type post 投稿タイプ
suppress_filters true SQL を書き換えるフィルターを効かせない

また、get_posts() は ignore_sticky_posts と no_found_rows を常に true にします。つまり、先頭固定表示の投稿を特別扱いせず、総件数を数えません。そのため ページ送りには向きません。

WP_Query の引数(category_name、tag、meta_query、date_query など)もそのまま渡せます。

基本の例

テンプレートタグを使わず、投稿オブジェクトから値を取り出す書き方が最も簡単です。

<?php
$recent_posts = get_posts( array(
	'numberposts'   => 5,
	'category_name' => 'news', // カテゴリーのスラッグ
) );

if ( $recent_posts ) {
	echo '<ul>';
	foreach ( $recent_posts as $recent ) {
		printf(
			'<li><a href="%s">%s</a></li>',
			esc_url( get_permalink( $recent ) ),
			esc_html( get_the_title( $recent ) )
		);
	}
	echo '</ul>';
}

the_title()などのテンプレートタグを使う場合

the_title() や the_permalink() はグローバル変数 $post を見ます。ループの中で使うときは setup_postdata() を呼び、最後に wp_reset_postdata() で元に戻します。関数の中で書く場合は global $post; が要ります。

<?php
global $post;
$recent_posts = get_posts( array( 'numberposts' => 5 ) );
foreach ( $recent_posts as $post ) {
	setup_postdata( $post );
	?>
	<h3><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h3>
	<?php
}
wp_reset_postdata();

日付で絞る例

date_query を使うと期間を指定できます。

<?php
$october = get_posts( array(
	'numberposts' => -1,
	'date_query'  => array(
		array(
			'after'     => '2026-10-01',
			'before'    => '2026-10-31',
			'inclusive' => true,
		),
	),
) );

numberposts を -1 にすると全件を読み込みます。件数が多いサイトでは、メモリと時間を使うので、上限を決めて取得します。

注意点

  • suppress_filters が true のため、posts_where などの SQL を書き換えるフィルターは効きません。多言語化プラグインなど、フィルターで絞り込む仕組みに合わせたい場合は 'suppress_filters' => false を指定します。
  • 利用者の入力($_GET など)を引数に使う場合は、absint() や sanitize_key() などで値を整えてから渡します。

WP_Queryでページ送りつきの一覧を作る

ページ送りが必要な別の一覧は、WP_Query で作ります。max_num_pages で総ページ数がわかります。

<?php
$paged = max( 1, get_query_var( 'paged' ) );
$news  = new WP_Query( array(
	'post_type'      => 'post',
	'category_name'  => 'news',
	'posts_per_page' => 10,
	'paged'          => $paged,
) );

if ( $news->have_posts() ) {
	while ( $news->have_posts() ) {
		$news->the_post();
		the_title( '<h3>', '</h3>' );
	}
	echo paginate_links( array( 'total' => $news->max_num_pages, 'current' => $paged ) );
	wp_reset_postdata();
}

固定ページのテンプレートの中でこの一覧を作ると、URL の /page/2/ がメインクエリ側で解釈されて404になることがあります。トップページやアーカイブの一覧そのものを変えたいなら、次の pre_get_posts を使うほうが不具合が起きにくくなります。なお、固定ページを静的フロントページにしている場合、ページ番号は paged ではなく page(get_query_var( 'page' ))に入ります。

pre_get_postsでメインクエリを変える

トップページに「blog カテゴリーだけを5件ずつ」出したい、という目的なら、テンプレートでクエリを作り直さず、メインクエリが実行される前に条件を変えます。テーマの functions.php(子テーマ推奨)か、自作プラグインに書きます。

<?php
add_action( 'pre_get_posts', function ( $query ) {
	if ( is_admin() || ! $query->is_main_query() ) {
		return;
	}
	if ( $query->is_home() ) {
		$query->set( 'category_name', 'blog' );
		$query->set( 'posts_per_page', 5 );
	}
} );
  • is_admin() で管理画面を除き、is_main_query() でメインクエリだけに限定します。これを忘れると、ウィジェットや管理画面の一覧まで変わります。
  • 条件分岐は is_home() ではなく $query->is_home() のように、渡されたクエリに対して呼びます。
  • テンプレート側は通常のループ(have_posts() / the_post())のままで、ページ送りも正しく動きます。

pre_get_posts の詳しい使い方は pre_get_postsの使い方 も参考にしてください。

ブロックテーマの場合

ブロックテーマ(Twenty Twenty-Five など)では、テンプレートは PHP ではなくブロックで作ります。投稿一覧は「クエリーループ」ブロックで条件を指定でき、PHP を書かずに済む場合が多くあります。メインクエリを変えたいときは、ブロックテーマでも pre_get_posts が使えます。

まとめ

  • query_posts() はメインクエリを上書きするため、公式に使用を避けるよう書かれている。既存コードは置き換える。
  • 少数の別一覧は get_posts()。既定は5件・新しい順・総件数を数えないので、ページ送りには使わない。
  • ページ送りが必要な別一覧は WP_Query。使った後は wp_reset_postdata()。
  • トップページやアーカイブの一覧を変えるなら pre_get_posts で、is_main_query() と ! is_admin() で対象を限定する。

お気軽にご相談ください

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