本文へ移動

WP_Query基礎

WordPressで追加した一覧にタイトル・本文を正しく表示する方法
|the_post()の使い方と役割

関連記事や新着記事をWP_Queryで追加するときは、取得した投稿を1件ずつ表示できる状態へ切り替える必要があります。have_posts()で次の投稿を確かめ、タイトルや本文を出す前にthe_post()を呼ぶ流れを、画面例とコードで確認します。

Huddly Huddly 投稿日: 2026年10月11日 読了目安: 7分

追加した一覧ではthe_post()をwhileの直後、表示の前に置く

関連記事や新着記事の一覧では、have_posts()で次の投稿があるかを確かめ、the_post()で表示する投稿へ切り替えます。続くthe_title()やthe_content()は、その投稿のタイトルと本文を表示します。

new WP_Query()の結果を$list_queryへ入れた場合は、whileの直後に$list_query->the_post()を1回呼びます。投稿IDを関数へ渡してタイトルやURLだけを取得する実装では、the_post()を使わない方法も選べます。

関連記事の一覧をテンプレートタグで表示する準備

関連記事や新着記事の一覧を追加するコードでは、have_posts()とthe_post()が続けて出てきます。名前が似ていても、前者は次の投稿があるかの確認、後者は表示する投稿の切り替えです。

この記事では、同じA・B・Cの3記事を使い、the_post()の位置だけを変えて表示を比べます。順番に出れば正常、同じ記事の繰り返しや1件ずれがあれば呼び出し方を見直す、という例です。コードのID 61・62・63がA・B・C、ID 64がブラウザで開く元のページに当たります。自分の環境で試すときは、実在する投稿IDへ置き換えてください。

$list_queryは、取得結果を受け取るためにこの例で付けた変数名です。この結果をwhileで1件ずつ表示する処理を、ここでは「追加ループ」と呼びます。今回は関連記事を自動で選ぶ方法ではなく、同じ3件でthe_post()の呼び出し位置を比べます。

new WP_Query()の基本的な書き方を先に確認したい場合は、次の記事から読むとコードの流れがつながります。

new WP_Query()は、条件に合う投稿を$list_query->postsへ保存します。ただし、取得しただけでは、the_title()やthe_content()がどの投稿を表示するかまでは切り替わりません。

処理役割この処理だけで変わるもの
have_posts()次に表示できる投稿があるか確認するtrue / falseを返す。ループ位置は進めない
the_post()取得結果を次の1件へ進め、投稿データを準備するcurrent_postが進み、global $postとget_the_ID()がその投稿を使う
the_title() / the_content()the_post()で準備した投稿を表示するタイトルや本文を出力する。次の投稿へは進めない

WordPress公式のWP_Query::the_post()は、次の投稿を取り出し、その投稿をループ内で使えるように準備します。the_post()自体がタイトルや本文を表示するわけではありません。なお、have_posts()は次の投稿へ進める関数ではありませんが、ループ終了時にはクエリ内の位置を巻き戻す処理も行います。

while ( $query->have_posts() )だけで回すと、ループ位置が進まないため同じtrue判定が続きます。ループ内で必ず次へ進む処理が必要です。

正しい位置はwhileの直後・テンプレートタグの前

whileの中で、記事を表示する前にthe_post()を書きます。次はクラシックテーマの例で、子テーマのtemplate-part.phpにあるPHPの処理へ追加する部分です。新しくこのファイルを作る場合は、先頭に<?phpを付けてからコードを入れます。すでにPHPが始まっている場所へ追加するなら、開始タグは不要です。

個別記事へ表示する場合は、実際に使われるsingle.phpのメインループ内で、the_content()の後などへget_template_part( ‘template-part’ );を追加して呼び出します。パーツを保存するだけでは表示されないため、表示元からの呼び出しも必要です。下のコードは、元の記事のthe_post()が実行された後に動かしてください。

クラシックテーマのテンプレート(またはテンプレートパーツ)で、一覧を表示したい位置に置く例です。投稿本文やfunctions.phpへ、そのまま貼るコードではありません。

PHPtemplate-part.phpthe_post()を正しい位置へ置く追加ループ
$list_query = new WP_Query(   [      'post_type'      => 'post',      'post_status'    => 'publish',      'posts_per_page' => 3,      'post__in'       => [ 61, 62, 63 ],      'orderby'       => 'post__in',      'ignore_sticky_posts' => true,   ]); if ( $list_query->have_posts() ) {   while ( $list_query->have_posts() ) {      $list_query->the_post();       echo '<article>';      the_title( '<h2>', '</h2>' );      the_content();      echo '</article>';   }} else {   echo '<p>表示できる投稿はありませんでした。</p>';} wp_reset_postdata();

have_posts()がtrueになった直後に$list_query->the_post()を呼ぶため、その下のthe_title()とthe_content()は同じ投稿を参照します。1周につき1回だけ呼ぶと、取得順どおりに1件ずつ進みます。

次の画像は、共通のサンプルサイトで実際にWP_Queryを動かした画面です。ページ内の関連記事に、確認用記事A・B・Cが上から取得順に並んでいます。下部の確認値でも、current_postが0・1・2と進んでいます。

同じサンプルページの関連記事に、確認用記事A・B・CがID 61・62・63の順で表示された画面
正常な表示A・B・Cを取得順に表示できている

緑枠の関連記事はA・B・Cの順です。下部のcurrent_postも0→1→2と進みます。

一覧で本文全体が不要なら、the_content()を抜粋や任意の表示へ変更できます。表示内容を変えても、テンプレートタグを使う前に投稿データを準備する順番は同じです。

このコードは本番と分けたWordPress環境で、A・B・Cが順番に表示されるか試します。検証や公開に使うサーバーをこれから用意する場合は、Xserverの料金やバックアップ条件も確認できます。サーバーを変えるだけでthe_post()の位置が直るわけではありません。

WordPress標準の一覧と、追加した一覧では呼び出し方が変わる

関数として書くthe_post()は、WordPressがURLに合わせて用意したメインクエリを進めます。一方、$list_query->the_post()は、この例で追加した一覧を進めます。->は、左側の$list_queryに対して右側の処理を呼び出す書き方です。このようにオブジェクトを通じて呼ぶ関数を「メソッド」と呼びます。どの一覧を進めるかによって、次のように書き分けます。

表示する一覧次の投稿を確かめる表示する投稿へ切り替える
WordPress標準のメイン一覧have_posts()the_post()
$list_queryで追加した一覧$list_query->have_posts()$list_query->the_post()

追加ループの中で関数the_post()を呼ぶと、意図した$list_queryではなくメインクエリを進めます。追加した一覧では、have_posts()とthe_post()の左側を同じクエリ変数にそろえます。

the_post()を呼ばないと表示する投稿が切り替わらない

WP_Queryを作ると、取得結果は$list_query->postsへ入ります。一方、テンプレートタグが使うglobal $postは、$list_query->the_post()を呼ぶまで元の記事のままです。

次は仕組みを説明するための失敗例です。投稿があると終了しないため、そのまま実行しないでください。最初のコードで作った$list_queryを使っても、この形ではループを次へ進める処理がありません。

PHPtemplate-part.phpthe_post()がなく進まない失敗例
while ( $list_query->have_posts() ) {   the_title();   the_content();}

このコードは実行し続けないでください。投稿がある場合、have_posts()の判定がtrueのままになり、同じ処理を繰り返します。タイトルと本文も独自クエリの投稿へ切り替わらず、直前までglobal $postに入っていた記事を表示します。

画像の検証では無限ループを避けるため、3回で打ち切るfor文で同じ状態を確認しています。3回ともcurrent_postは-1のままで、画面には取得したA・B・Cではなく、元の確認ページが繰り返し表示されました。

同じサンプルページの関連記事に、元の確認ページID 64が3回表示された画面
呼ばない場合ループが開始前のまま進まない

赤枠の3件がすべて元のページID 64になっています。current_postは3回とも-1のままです。

早すぎると先頭を飛ばし、遅すぎると1件ずれる

the_post()は、呼ぶ位置が早すぎても遅すぎても表示がずれます。

置いた位置起きること3件A・B・Cの表示
whileの直後、表示前the_post()で準備した投稿をそのまま表示できるA、B、C
whileより前にも1回呼ぶAを表示前に消費するB、C
the_title()などの後表示時点では1つ前の投稿を使う元の記事、A、B。Cは準備後にループ終了
呼ばない位置が開始前から進まない元の記事を表示したまま判定が続く

ループ前に先頭投稿だけ準備してから、ループ内でもthe_post()を呼ぶと、最初に表示するまでに2回進むため、Aを飛ばしてBから表示します。反対に、テンプレートタグの後で呼ぶと、表示と投稿データの準備が1周ずつずれます。

迷ったら「have_posts()で次の投稿を確認 → the_post()で表示対象を切り替える → テンプレートタグで表示」の順番になっているかを見直します。

次の画面では、表示したあとにthe_post()を呼んでいます。1回目は元の記事、2回目は確認用記事A、3回目は確認用記事Bとなり、確認用記事Cは表示されません。

同じサンプルページの関連記事が、元のページID 64、確認用記事A、確認用記事Bの順になった画面
呼ぶ位置が遅い場合表示が1件ずつ遅れ、最後のCが出ない

赤枠は元のページ→A→Bの順で、Cがありません。current_postは表示時点で-1→0→1です。

テンプレートタグはthe_post()で準備した投稿を使う

get_the_ID()は投稿IDを引数として受け取らず、the_post()で切り替えた投稿のIDを返します。the_title()はその投稿のタイトルを、the_content()はその投稿の本文をフィルターへ通して表示します。

そのため、取得したい投稿IDをget_the_ID()へ渡して表示対象を変えることはできません。追加ループでは先に$list_query->the_post()で表示する投稿を準備するか、IDや投稿オブジェクトを受け取れる取得関数を使います。

関数どの投稿を使うか主な用途
get_the_ID()the_post()で切り替えた投稿追加ループ内の投稿IDを取得
the_title()the_post()で切り替えた投稿追加ループ内のタイトルを表示
the_content()the_post()で切り替えた投稿追加ループ内の本文をフィルター後に表示
get_the_title( $post_id )引数で指定したIDまたは投稿global $postを変えずにタイトルを取得
get_permalink( $post_id )引数で指定したIDまたは投稿global $postを変えずにURLを取得

タイトルやURLだけを扱う小さな処理では、IDを指定する方が分かりやすいことがあります。本文表示はthe_content()のフィルター処理やループの状態も関係するため、通常の一覧ではhave_posts()、the_post()、テンプレートタグの順に書く方法が分かりやすいです。

the_post()を使わない方が単純なケース

取得した投稿IDを別処理へ渡すだけ、タイトルとURLをID指定で組み立てるだけ、件数を確認するだけなら、global $postを切り替えずに処理できます。

クラシックテーマのテンプレート(またはテンプレートパーツ)で、一覧を表示したい位置に置く例です。投稿本文やfunctions.phpへ、そのまま貼るコードではありません。

PHPtemplate-part.php投稿IDを明示してthe_post()を使わずに表示する
$id_query = new WP_Query(   [      'post_type'      => 'post',      'post_status'    => 'publish',      'posts_per_page' => 3,      'fields'         => 'ids',      'post__in'       => [ 61, 62, 63 ],      'orderby'       => 'post__in',      'ignore_sticky_posts' => true,   ]); if ( $id_query->posts ) {   echo '<ul>';    foreach ( $id_query->posts as $post_id ) {      printf(         '<li><a href="%1$s">%2$s</a></li>',         esc_url( get_permalink( $post_id ) ),         esc_html( get_the_title( $post_id ) )      );   }    echo '</ul>';}

この例は$id_query->the_post()やsetup_postdata()を呼ばないため、global $postを切り替えません。そのため、この処理だけを理由にwp_reset_postdata()を呼ぶ必要もありません。

ただし、the_title()、the_content()、投稿IDを省略したget_permalink()など、ループで切り替えた投稿を使うテンプレートタグへ戻す場合は、基本のループ構造を使う方が分かりやすいです。

投稿IDを使う方法と通常のループの違いを確認できたら、次は投稿タイプや件数をご自身の一覧に合わせます。WP Query Builderで取得条件を作り、表示する処理はこの記事の例と組み合わせます。

一覧の取得条件を組み立てる

投稿タイプ・絞り込み・件数を整理し、生成した$argsをループへ渡します。the_post()を使う場合は、呼び出し位置と最後のリセットも確認してください。

追加ループを使ったあとはwp_reset_postdata()で戻す

追加クエリのthe_post()は、global $postをループ中の各投稿へ切り替えます。ループが終わった直後にwp_reset_postdata()を置き、その後のテンプレートタグが、ブラウザで開いている記事を再び参照できる状態へ戻します。

リセットが必要かは、WP_Queryを作ったかではなく、the_post()やsetup_postdata()で投稿データを切り替えたかで判断します。戻し忘れで別タイトルが出る症状や、リセット関数の違いを詳しく知りたい場合は、次の記事もあわせてご覧ください。

同じ3件で比べると、呼び出し位置による違いが分かる

3枚の画面の違いを、ループの位置でも比べてみましょう。表のcurrent_postは表示する時点の値で、-1は開始前、0・1・2はA・B・Cに当たります。終了後はhave_posts()によって巻き戻されるため、ループの外で記録した値とは区別してください。値は上の画像と同じ、ローカルのWordPress 7.1/PHP 8.4.20で取得しています。

条件表示時のcurrent_postglobal $post / get_the_ID()表示対象
開始前-164 / 64元の確認ページ
while直後にthe_post()0、1、261、62、63で一致確認用記事A、B、C
the_post()なしで3回確認-1のまま3回とも643回とも元の確認ページ
while前にも1回呼ぶ1、262、63確認用記事B、C
表示後に呼ぶ-1、0、164、61、62元の確認ページ、確認用記事A、B。Cは未表示

投稿オブジェクトをget_the_title( $item )やget_the_content( null, false, $item )へ渡す場合は、対象の投稿を明示して値を取得できます。投稿を指定して値を取る方法と、テンプレートタグ用の表示対象を切り替える方法は分けて考えます。

なお、get_the_content()は投稿を指定できますが、それだけではthe_content()と同じフィルター処理を行いません。公開画面で本文を通常どおり表示する場合は、ループ内でthe_post()の後にthe_content()を使う形を基本にします。

表示がずれるときは、ループ位置と投稿IDを比べる

タイトルだけを見比べると、同名投稿や1件遅れを見落とすことがあります。最初のコードを置いたテンプレートパーツで、$list_query->the_post()の直後に次のログを一時的に追加します。ループ位置と3種類の投稿IDに加え、タイトルも記録します。

PHPtemplate-part.phpループ位置と投稿参照をローカルログへ記録する
global $post; error_log(   wp_json_encode(      [         'current_post'  => $list_query->current_post,         'query_post_id' => $list_query->post instanceof WP_Post            ? $list_query->post->ID            : null,         'global_post_id'=> $post instanceof WP_Post ? $post->ID : null,         'get_the_ID'    => get_the_ID(),         'title'         => get_the_title(),      ],      JSON_UNESCAPED_UNICODE   ));

wp-config.phpでWP_DEBUGとWP_DEBUG_LOGを有効にしている場合、通常はWordPressのwp-content/debug.logで確認できます。画面にログを出さないため、WP_DEBUG_DISPLAYはfalseにします。確認用のログはローカルやステージング環境だけで使い、原因が分かったら削除してください。

正常なら、query_post_id、global_post_id、get_the_ID()が各周で同じIDになり、current_postが0から1ずつ増えます。

have_posts()とthe_post()の公式資料

have_posts()が確認する内容、the_post()が投稿データを準備する順序、テンプレートタグの参照先とループ後の戻し方は、次の公式資料で確認できます。

WordPress公式

確認に使ったWordPress公式ドキュメント

取得した記事を順番に表示できたか確かめる

最初の記事から最後の記事まで、抜けや繰り返しなく表示されているかを見ます。一覧の後にあるタイトルや本文も元の記事のままなら、最後のリセットまで確認できています。

the_post()の最終確認

  • have_posts()とthe_post()の役割を分けた
  • 追加した一覧では同じクエリ変数のthe_post()を使った
  • while直後・テンプレートタグ前に1回だけ置いた
  • global $post、get_the_ID()、クエリの投稿IDが各周で一致した
  • 追加ループ後にwp_reset_postdata()を書いた
WP Query BuilderBuilderで条件を組み直す条件を選び、WP_Queryのコードを画面で作れます。Builderでクエリを作る

この記事を書いた人

Huddly

Web制作 / フロントエンドエンジニア

WordPressとフロントエンド実装で迷いやすい点を、公式情報と検証結果を分けながら、初学者にも追いやすい順序で整理しています。

この記事の検証情報

検証環境

WordPress
7.1.4
PHP
8.3.36
テーマ
wpq-media + DDEV
対象
カスタム投稿 / taxonomy / meta_query

更新履歴

  • 2026年10月11日 初回公開

この記事を活用する

あとで見返す・共有する

お気に入りに保存できます。役に立ったポイントは、いいねや共有で教えてください。

任意のご支援 役に立ったときだけ、活動を応援できます OFUSEはサイト・記事制作に使います。支援の有無で、読める内容や使える機能は変わりません。