本文へ移動

実装例

WP_Queryで表示件数とページネーションを設定する方法
|posts_per_page・pagedの基本

WP_Queryのページネーションには、1ページの件数、現在ページ、総ページ数、ページリンクの4つが必要です。取得側とリンク側へ同じ現在ページを渡す完成コードから確認します。

Huddly Huddly 投稿日: 2026年10月15日 読了目安: 8分

件数・現在ページ・総ページ数・リンクを同じクエリでつなぐ

WP_Queryでページ送りを作るには、posts_per_pageで1ページの件数を決め、現在ページをpagedへ渡します。リンク側では同じ現在ページをcurrent、独自クエリのmax_num_pagesをtotalへ渡します。

4つの値を同じクエリでそろえると、1ページ目と2ページ目の取得結果、現在位置、最終ページが一致します。

ページネーションは4つの値でできている

表示件数だけ指定しても、WP_Queryは現在が何ページ目か分かりません。ページリンクを表示するだけでは投稿は切り替わらず、取得側へ現在ページを渡す必要があります。件数・現在ページ・総ページ数・リンクを、次のようにつなぎます。

役割WP_Queryまたは関数設定する値
1ページの件数WP_Queryのposts_per_page1以上の整数
現在ページの取得get_query_var( 'paged', 1 )URLに対応するページ番号
現在ページの取得条件WP_Queryのpaged取得したページ番号
総ページ数WP_Queryのmax_num_pages取得後に計算されたページ数
ページリンクpaginate_links()のcurrentとtotal同じ現在ページと総ページ数
追加したWP_Queryのページ送りでは、メインクエリではなく、その追加クエリのmax_num_pagesを使います。

WP_Queryとpaginate_links()をつないだ完成例

次のコードは、検証用の通常投稿6件(ID 10〜15)を3件ずつ取得し、番号付きページネーションを表示します。画像・表でも同じ6件を使います。IDはご自身の検証環境の投稿へ置き換え、通常運用で全投稿を対象にするときはpost__inを外してください。

クラシックテーマの子テーマへtemplate-parts/paginated-posts.phpとして保存します。固定フロントページ以外の一覧用テンプレートで、表示したい場所のPHP処理へget_template_part( ‘template-parts/paginated-posts’ );を追加して呼び出してください。ファイルを保存するだけでは一覧は出ません。WordPress標準のアーカイブそのものを変更する場合は、後のメインクエリの例を使います。

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

PHPtemplate-parts/paginated-posts.php同じ6件の投稿を3件ずつ表示してページリンクを作る
<?php$current_page = max( 1, (int) get_query_var( 'paged', 1 ) ); $list_query = new WP_Query(   [      'post_type'           => 'post',      'post_status'         => 'publish',      'post__in'            => [ 10, 11, 12, 13, 14, 15 ],      'orderby'             => 'post__in',      'posts_per_page'      => 3,      'paged'               => $current_page,      'ignore_sticky_posts' => true,      'no_found_rows'       => false,   ]); if ( $list_query->have_posts() ) {   echo '<ul>';    while ( $list_query->have_posts() ) {      $list_query->the_post();       printf(         '<li><a href="%1$s">%2$s</a></li>',         esc_url( get_permalink() ),         esc_html( get_the_title() )      );   }    echo '</ul>';    $placeholder = '999999999';   $pagination  = paginate_links(      [         'base'      => str_replace(            $placeholder,            '%#%',            get_pagenum_link( $placeholder, false )         ),         'format'    => '?paged=%#%',         'current'   => $current_page,         'total'     => $list_query->max_num_pages,         'type'      => 'list',         'prev_text' => '前へ',         'next_text' => '次へ',      ]   );    if ( $pagination ) {      echo '<nav aria-label="投稿一覧のページ送り">';      echo wp_kses_post( $pagination );      echo '</nav>';   }} else {   echo '<p>表示できる投稿はありません。</p>';} wp_reset_postdata();

新しく作るパーツには、先頭の<?phpも含めて保存します。すでにPHPが始まっている場所へ追加するなら、開始タグは不要です。get_pagenum_link()は現在のパーマリンク設定に合わせたページURLの土台を作ります。第2引数をfalseにし、リンク生成前にはHTML用のエスケープをしません。既存のクエリ文字列がある場合のURL破損を避け、生成後のHTMLをwp_kses_post()で出力します。

paginate_links()は総ページ数が2未満ならnullを返すため、戻り値がある場合だけnavを表示しています。

999999999は実際のページ番号ではありません。ページURLの中でほかの数字と重なりにくい仮の番号として入れ、直後のstr_replace()で%#%へ置き換えます。paginate_links()は、この%#%へ1、2、3のようなページ番号を入れてリンクを作ります。

type=’list’にすると、リンクはulとliを含むリスト形式のHTMLで返ります。wp_kses_post()は、WordPressの記事本文で許可される安全なHTMLだけを残して出力する関数です。この例では、navの開始タグ、ページリンク、終了タグを順にechoし、ページ送り全体を1つのナビゲーションとして表示しています。

独自ループでthe_post()を使った後は、wp_reset_postdata()で後続のテンプレートタグがメイン側の投稿を参照する状態へ戻します。

完成例は本番と分けたWordPress環境で、1ページ目と2ページ目の投稿・URLを比べます。検証や公開に使うサーバーをこれから用意する場合は、XserverのPHP対応やバックアップ条件も確認できます。

posts_per_pageで1ページの表示件数を決める

posts_per_pageは、1ページにつきWP_Queryが取得する投稿数です。3を指定すれば、条件に一致する投稿を最大3件取得します。最終ページは残り件数だけになるため、常に3件とは限りません。

指定動作ページ送り
31ページ最大3件総件数からページ数を計算
11ページ1件投稿数と同じだけページができる
未指定表示設定の1ページ最大投稿数を使用サイト設定に依存
0通常は未指定と同様に表示設定の値を使う0件指定にはならない
-1全件取得通常のページ分割をしない

全件表示が必要でも、投稿が増えるページでは-1を安易に使わず、上限を決めます。通常のページ送りを作る記事では、1以上の整数を指定してください。

pagedへ現在ページ番号を渡す

通常のアーカイブや一覧URLでは、get_query_var( ‘paged’, 1 )から現在ページを取得します。1つ目の引数pagedは取得したい値の名前、2つ目の引数1は値がないときの初期値です。1ページ目では0になる構成もあるため、max( 1, … )で1以上にそろえます。

引数や処理の一部分だけを示した例です。このまま単独で貼らず、テンプレート内のWP_Queryとループへ組み込んでください。

PHPpagination-current-page.php通常一覧の現在ページを1以上の整数で取得する
$current_page = max( 1, (int) get_query_var( 'paged', 1 ) ); $args = [   'posts_per_page' => 3,   'paged'          => $current_page,];

これは完成例から現在ページの取得と設定だけを抜き出したコードです。同じテンプレートパーツで取得した$current_pageを、WP_Queryのpagedとpaginate_links()のcurrentの両方へ渡します。取得側とリンク側の値を別々に計算しないことが重要です。

ページリンクのURLが2ページ目になっても、WP_Queryへpagedを渡していなければ取得条件は毎回1ページ目のままです。現在ページは、リンク側より先に取得側へ渡します。

表示件数と現在ページの役割が分かったら、取得条件をWP Query Builderで整理できます。画面で指定したページ番号は固定値なので、実装時はURLから取得した$current_pageへ置き換えます。

ページ送り用の取得条件を作る

posts_per_pageとpagedを指定し、no_found_rowsをfalseにした$argsを作ります。現在ページの取得とページリンクの表示は、この記事のテンプレートコードでつなぎます。

found_postsとmax_num_pagesの役割を分ける

WP_Queryの実行後は、post_count、found_posts、max_num_pagesで件数を確認できます。ページリンクのtotalへ渡すのは、総件数ではなく総ページ数です。6件を3件ずつ表示する例で、違いを見てみましょう。

値意味今回の6件の例
post_count現在ページで取得した件数各ページ3件
found_posts条件に一致した総件数6件
max_num_pages総ページ数6件を3件ずつで2ページ

max_num_pagesは、総件数と1ページの件数から計算された結果です。追加した一覧を$list_queryで取得したなら、$list_query->max_num_pagesを使います。グローバル$wp_query->max_num_pagesを使うと、ブラウザで開いているページと追加した一覧の総ページ数が混ざります。

paginate_links()にはメインクエリを使う既定値があります。独自WP_Queryでは、現在ページと総ページ数を明示し、どのクエリのリンクかをそろえます。次は完成例のリンク生成部分の抜粋で、$current_pageと$list_queryを作った同じテンプレートパーツ内で使います。

引数や処理の一部分だけを示した例です。このまま単独で貼らず、テンプレート内のWP_Queryとループへ組み込んでください。

PHPpagination.php独自WP_Queryのページリンクだけを生成する
$placeholder = '999999999'; $pagination = paginate_links(   [      'base'      => str_replace(         $placeholder,         '%#%',         get_pagenum_link( $placeholder, false )      ),      'format'    => '?paged=%#%',      'current'   => $current_page,      'total'     => $list_query->max_num_pages,      'type'      => 'list',      'prev_text' => '前へ',      'next_text' => '次へ',   ]);

currentは現在ページ、totalは総ページ数です。typeをlistにするとリスト形式のHTMLが返ります。この抜粋は生成までなので、表示するときは最初の完成例と同じように、戻り値がある場合だけwp_kses_post( $pagination )をechoします。文字列連結そのものが問題なのではなく、HTMLを適切に扱って出力することが重要です。

typeの指定戻り値使う場面
plainまたは未指定リンクをつないだ文字列自分で外側のHTMLを用意する
arrayリンクごとの配列1件ずつ加工して表示する
listulとliを含むHTML番号付きページ送りをリストとして表示する

固定フロントページではpageも確認する

固定フロントページ上のページテンプレートで独自一覧を表示する場合、現在ページ番号がpagedではなくpageへ入る構成があります。WordPress公式のget_query_var()リファレンスも、固定フロントページではpageを使う例を示しています。

引数や処理の一部分だけを示した例です。このまま単独で貼らず、テンプレート内のWP_Queryとループへ組み込んでください。

PHPfront-page.php固定フロントページで現在ページ番号を取得する
$current_page = max( 1, (int) get_query_var( 'page', 1 ) ); $front_query = new WP_Query(   [      'post_type'      => 'post',      'post_status'    => 'publish',      'post__in'       => [ 10, 11, 12, 13, 14, 15 ],      'orderby'        => 'post__in',      'posts_per_page' => 3,      'paged'          => $current_page,      'ignore_sticky_posts' => true,      'no_found_rows'  => false,   ]);

ここではURLからページ番号を読むときにpageを使いますが、追加のWP_Queryへ渡すパラメーター名はpagedです。つまり「WordPressから読む名前」と「WP_Queryへ渡す名前」が異なります。front-page.phpで使う取得部分の例なので、表示ループとページリンクでは変数名を$front_queryへ揃え、リンクのcurrentには同じ$current_pageを渡します。トップページだけ2ページ目が同じになる場合は、「設定 → 表示設定」で固定フロントページかを確認し、pagedとpageの値をローカルで比べます。

既存のアーカイブ一覧は作り直さない

カテゴリー、検索、投稿タイプアーカイブなど、WordPressが元から用意する一覧そのものを変えたい場合は、テンプレートで同じ投稿を独自WP_Queryし直す前に、メインクエリの変更を検討します。

対象を限定したpre_get_postsでposts_per_pageを変更すれば、同じメインクエリのfound_postsとmax_num_pagesが更新されます。テーマのページネーションもメインクエリへつながるため、独自の現在ページと総ページ数を別管理せずに済むことがあります。次はアーカイブを有効にした投稿タイプnewsが登録済みの場合の例で、子テーマのfunctions.phpまたは専用プラグインへ1回だけ置きます。

PHPfunctions.phpnewsアーカイブのメイン一覧を12件へ変更する
function wptl_change_news_archive_count( WP_Query $query ) {   if ( is_admin() || ! $query->is_main_query() ) {      return;   }    if ( $query->is_post_type_archive( 'news' ) ) {      $query->set( 'posts_per_page', 12 );   }}add_action( 'pre_get_posts', 'wptl_change_news_archive_count' );

add_action()は、WordPressがpre_get_postsを実行したときに、wptl_change_news_archive_count()を呼ぶための登録です。関数の中で、管理画面ではない、メインクエリである、newsアーカイブである、という順に対象を絞っています。

この例では、テンプレート側の通常ループとページリンクをそのまま使います。追加した一覧を作る目的なら、最初の追加WP_Queryの完成例を使います。

正常なページ送りでは3つの指定を避ける

総ページ数を使って数字のリンクを出す場合、次の3つを加えるとページ送りが崩れる原因になります。最初の完成例では使わず、必要になったときに右の各記事で条件を確認してください。

指定避ける理由次に確認する記事
no_found_rowsをtrue通常の総件数と総ページ数を計算しないno_found_rowsの使い分け
posts_per_pageを-1全件取得になりページ分割しない全件取得が重くなる理由
offsetを固定値で追加pagedとの取得位置とページ数がずれるoffsetとページネーションの併用

no_found_rows=trueは、総件数を数える処理を省いて軽くする指定です。その代わり、通常のmax_num_pagesを計算できないため、ページ送りが必要な一覧では使いません。

最初はposts_per_pageとpagedだけで1ページ目・2ページ目を確認します。正常に動いたあとで絞り込みや並び順を1つずつ追加すると、どの条件で崩れたか追いやすくなります。

WP Query Builderでは取得条件を作り、ページ番号とリンクはテンプレートでつなぐ

WP Query Builderでは、投稿タイプ、表示件数、並び順、絞り込みなど、WP_Queryへ渡す$argsを整理できます。ページネーションを使う場合は、総ページ数が必要なのでno_found_rowsを有効にしない構成を確認します。

現在URLからページ番号を読む処理、固定フロントページのpage判定、paginate_links()のHTML出力はテンプレート側で追加します。Builderで生成した$argsへpagedを渡し、同じクエリのmax_num_pagesをリンクへつなぎます。

Builderの画面操作は公式チュートリアルで詳しく説明しています。この記事では、生成後のコードとページリンクをWordPress側でつなぐ部分を扱います。

同じ6件で投稿と現在ページの切り替わりを比べる

6件の記事を3件ずつ表示すると、1ページ目は最初の3件、2ページ目は残りの3件になります。投稿の内容だけでなく、ページ番号の現在位置も一緒に切り替わるかを見てください。次の値と画面は、本文と同じ6件をローカルのWordPress 7.1/PHP 8.4.20で表示したものです。

確認項目1ページ目2ページ目
paged12
post_count33
found_posts66
max_num_pages22
投稿ID10、11、1213、14、15
ページ間の重複なしなし

まず1ページ目には、最初の3件が出ます。番号の「1」が現在位置です。

共通サンプルサイトのお知らせ一覧でID 10・11・12が表示され、ページ送りの1が現在位置になった画面
1ページ目先頭の3投稿を表示する

1ページ目はID 10・11・12です。全6件を3件ずつ表示するため、ページ送りは2ページまであります。

「2」のリンクを開くと、次の3件へ進み、現在位置も「2」へ移ります。

同じお知らせ一覧でID 13・14・15へ切り替わり、ページ送りの2が現在位置になった画面
2ページ目投稿と現在ページが一緒に切り替わる

同じサイトの2ページ目はID 13・14・15です。総件数と総ページ数を保ったまま、次の3件へ進んでいます。

同じ総件数と総ページ数を保ちながら、ページごとの投稿IDが次の3件へ切り替わります。paginate_links()へcurrent=2とtotal=2を渡すと、現在ページ2のaria-currentを含むHTMLが生成されます。2枚は同じサイトの1ページ目と2ページ目を、それぞれ撮影したものです。検証環境では専用のURL変数fixture_pageでページを切り替えていますが、本文コードへその名前を追加する必要はありません。

固定フロントページについては、公式仕様に従いpageを取得元とするコードを分離しました。実サイトでは表示設定を確認し、対象トップページでpageとpagedの実値を記録してください。

実装後は最後のページまで確認する

変更範囲を広げる前に、次の順番で原因を絞ります。

ページ送りの確認順

  1. 1
    1ページ目でposts_per_pageどおりの件数か確認する
  2. 2
    2ページ目でURL、current、投稿IDが変わるか確認する
  3. 3
    1ページ目と2ページ目で投稿IDの重複・欠落がないか確認する
  4. 4
    最終ページの件数とmax_num_pagesを確認する
  5. 5
    最終ページを1つ超えたURLの挙動を確認する
  6. 6
    固定フロントページならpageとpagedを別に記録する

記事の追加や削除で最終ページは変わります。テスト用の固定件数だけでなく、0件、1ページだけ、2ページ以上、最終ページが端数になる条件を確認してください。

設定時によくある間違い

実装や公開へ進む前に、見落としやすい項目をまとめて確認します。

ページ送りが動かないときに見直す項目

  • posts_per_pageだけを指定し、現在ページをpagedへ渡していない
  • 2ページ目リンクを作っただけで取得記事も切り替わると思っている
  • 追加WP_Queryのリンクにメインクエリのmax_num_pagesを使っている
  • paginate_links()のtotalへfound_postsを渡している
  • 固定フロントページでもpagedだけを読んでいる
  • ページ送りが必要なのにno_found_rowsをtrueにしている
  • posts_per_page=-1で全件取得しながらリンクを出そうとしている
  • offsetを追加したのに取得位置と総ページ数を補正していない
  • 追加ループ後にwp_reset_postdata()を書いていない

リンクが出ない、2ページ目が同じ、404になるなどの症状がすでにある場合は、WP_Queryでページネーションが動かない原因で症状別に確認します。表示件数だけが合わない場合は、posts_per_pageの確認方法もご覧ください。

WP_Queryとpaginate_links()の公式資料

posts_per_pageとpagedで取得する範囲、max_num_pagesと現在ページをpaginate_links()へ渡す方法は、次の公式資料で確認できます。

WordPress公式

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

リンクと投稿の両方が切り替わるか確かめる

リンクを押してURLが変わるだけでなく、表示される投稿と現在ページの番号も変われば、取得側とリンク側がつながっています。投稿が少ない場合や増減した場合も、最後のページまで確かめてから本番へ反映しましょう。

本番へ反映する前に

  • 検証環境で動作を確かめ、変更前のファイルへ戻せる
  • 0件・1ページだけ・複数ページ・最終ページが端数の表示を確認した
  • ページリンクのURL・現在位置・投稿が一緒に切り替わり、重複や欠落がない
  • 一覧の後のタイトルや本文が元の記事のままである
  • スマホとキーボードでページ番号を操作できる
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月15日 初回公開

この記事を活用する

あとで見返す・共有する

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

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