本文へ移動

実装例

固定ページの子ページを一覧にする方法|WP_Queryのpost_parentと全子孫の取得

固定ページの親子関係から一覧を作るときは、直下の子だけか、孫以下も含めるかを先に決めます。post_parentとchild_ofの違いを、実際の固定ページと取得結果で確認しましょう。

Huddly Huddly 投稿日: 2026年11月5日 読了目安: 5分
Xで共有 Facebook
PR

広告リンクを含みます。申込により報酬が発生する場合がありますが、記事の評価には影響しません。

直下の子と、孫以下を含む子孫で取得方法を分ける

直下の子ページだけなら、WP_Queryでpost_typeをpage、post_parentを親ページのIDにします。孫以下も含めたい場合は、get_pages()のchild_ofを使えます。親のIDと公開状態を確認してから、必要な範囲に合う方法を選びましょう。URLの階層だけでは、固定ページの親子関係は判断できません。

子だけか、孫以下も含めるかを決める

「サービス」の下に「制作」と「保守」があり、制作の下に「料金」がある例を考えます。サービスの直下だけなら制作と保守、孫以下も含むなら料金も取得対象です。サービス自身はどちらの一覧にも含めません。

必要な範囲と引数の対応は、次のように分かれます。

表示したい範囲取得方法親IDを入れる引数
直下の子だけWP_Querypost_parent
孫以下を含む子孫get_pages()child_of
get_pages()で直下の子だけget_pages()parent

post_parentは直接の親IDを条件にします。親IDを指定しても、その子の下にある孫ページまで自動的にたどる指定ではありません。また、get_pages()のparentとchild_ofも、同じ意味で入れ替えて使う引数ではありません。

固定ページの親設定・公開状態・順序を確認する

親IDは管理画面で対象の固定ページを編集し、URLのpost=112のような値から確認できます。子側の「親」設定も、その固定ページになっているかを確かめてください。スラッグやURLの一部を、IDの代わりには使いません。

以下は検証用サイトに用意した架空の固定ページです。親のサービスはID112、制作はその子、料金は制作の子になっています。順序の値であるmenu_orderは保守が10、制作が20、料金が5です。

検証用の固定ページ管理画面で、サービス、制作、保守、料金と下書き・非公開ページのID、親ID、順序を確認した画面
親子関係の確認子ページ側に保存された親IDを確認する

サンプルの親はサービス(112)です。料金(115)の親は制作(113)なので、サービスの孫にあたります。ID・親ID・順序の列は検証用に追加し、実際の保存値を表示しています。

WP_Queryで直下の子ページを一覧表示する

次は、サービスを親に持つ公開済みの固定ページを表示する例です。クラシックテーマの子テーマにtemplate-parts/child-pages.phpを作り、固定ページ用テンプレートの一覧を置きたい位置からget_template_part()で呼び出します。引数にはtemplate-parts/child-pagesを指定します。投稿本文やfunctions.phpへ、そのまま貼るコードではありません。

112はサンプルの親IDです。実サイトの固定ページIDへ置き換えてください。正の値と固定ページであることを確認し、IDの取得に失敗した場合はこのパーツの処理を終了します。

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

PHPtemplate-parts/child-pages.php指定した親の直下にある公開ページを表示する
<?php$parent_id = 112;if ( $parent_id <= 0 || get_post_type( $parent_id ) !== 'page' ) {    return;} $children = new WP_Query( [    'post_type'      => 'page',    'post_status'    => 'publish',    'post_parent'    => $parent_id,    'posts_per_page' => -1,    'orderby'       => [ 'menu_order' => 'ASC', 'title' => 'ASC' ],    'no_found_rows'  => true,] ); if ( $children->have_posts() ) {    echo '<ul>';    while ( $children->have_posts() ) {        $children->the_post();        echo '<li><a href="' . esc_url( get_permalink() ) . '">'            . esc_html( get_the_title() ) . '</a></li>';    }    echo '</ul>';} else {    echo '<p>公開中の子ページはありません。</p>';}wp_reset_postdata();

取得する投稿タイプをpageにし、親IDと公開状態を絞るのが基本です。並び順はmenu_orderの小さい順で、同じ値ならタイトル順にします。このサンプルでは保守、制作の順になり、孫の料金や下書き・非公開のページは入りません。

ループ内のthe_post()で表示対象が切り替わるため、一覧の後でwp_reset_postdata()を呼びます。一覧の下に続く固定ページのタイトルなどが、最後の子ページへ変わっていないかも確認しましょう。

posts_per_pageの-1は全件取得です。小さな案内ページ群を想定しているので、大量の固定ページを持つサイトでは件数を区切り、ページ送りも含めて設計してください。この例はページ送りを作らないため、総件数の計算を省くno_found_rowsを有効にしています。

親IDや公開状態を変える試験は、検証環境で行います。公開先を選ぶ場合も、今回のような一覧変更に備えて、バックアップと復元方法を確認しておくと安心です。

孫以下も含めるならget_pagesのchild_ofを使う

同じサービスから、制作の下にある料金まで取得するならget_pages()のchild_ofへ親IDを渡します。次は子テーマのtemplate-parts/descendant-pages.phpに置く例です。前のパーツと両方を使う必要はなく、求める範囲に合わせて片方を選び、固定ページ用テンプレートから呼び出します。

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

PHPtemplate-parts/descendant-pages.php指定した固定ページの子孫を一覧表示する
<?php$parent_id = 112;if ( $parent_id <= 0 || get_post_type( $parent_id ) !== 'page' ) {    return;} $descendants = get_pages( [    'post_type'   => 'page',    'post_status' => 'publish',    'child_of'    => $parent_id,    'sort_column' => 'menu_order,post_title',    'sort_order' => 'ASC',] ); if ( $descendants ) {    echo '<ul>';    foreach ( $descendants as $page_item ) {        echo '<li><a href="' . esc_url( get_permalink( $page_item ) ) . '">'            . esc_html( get_the_title( $page_item ) ) . '</a></li>';    }    echo '</ul>';} else {    echo '<p>公開中の子孫ページはありません。</p>';}

この条件では、保守、制作、料金の順に取得されます。料金のmenu_orderは5ですが、全ページを単純に5、10、20と並べる結果にはなりません。子孫を親子の階層に沿ってたどるため、制作の後にその子の料金が続きます。同じ階層の並び順と、階層をたどる順序を分けて考えてください。

また、取得対象に孫を含めても、この表示コードが作るのは1段のリストです。深さに合わせた入れ子のメニューは作っていません。各関数へページを明示して渡し、the_post()やsetup_postdata()でグローバルな表示対象を切り替えていないため、この例にはwp_reset_postdata()を追加する必要がありません。

直下の子を出すWP_Queryを自分のIDで作るときは、Builderの詳細設定でもpost_type=pageとpost_parentを指定できます。生成された$argsのorderbyをこの記事の配列へ変更し、表示ループを組み合わせます。get_pages()のコードを生成する機能ではありません。

固定ページの親IDで取得条件を作る

詳細設定の「投稿ID・スラッグ・親子関係」でpost_parentを選び、自分の親IDを入力します。記事から条件が自動入力される導線ではありません。

同じ固定ページで取得結果を見比べる

次は、上のPHPを検証用サイトのWordPress 7.1・PHP 8.4.20で実行した結果です。左側の直下の子一覧は保守と制作、右側の全子孫一覧には料金も加わります。どちらの一覧の後でも、元のサービスのタイトルとID112を表示できています。

サービスの直下の子は保守と制作、全子孫は保守と制作と料金になり、各一覧後のタイトルがサービスへ戻っている実行画面
実際の表示結果post_parentとchild_ofで取得範囲が変わる

公開済みだけを対象にした結果です。直下は114・113、全子孫は114・113・115の順です。右の表示も1段のリストで、料金を入れ子にはしていません。

出ない・多すぎるときは親IDと公開状態を確認する

まず、対象ページに保存された親IDとコードの値が一致しているかを確認します。孫だけが出ないなら、post_parentの指定ミスではなく、求めている取得範囲と方法が違う可能性があります。

特に、WP_Queryのpost_parent=0は親を持たないページを対象にします。一方、get_pages()のchild_of=0は子孫の対象を限定しない値です。どちらも「IDが分からないので0にしておく」という使い方を避けてください。本文の2つの例は、0や不存在のIDを処理前に除外しています。有効な親に公開中の対象ページがなければ、0件用のメッセージが出ます。

公開状態が混在する階層にも注意が必要です。検証用の制作を下書きに変えると、サービスを親にしたget_pages()の公開子孫一覧は保守だけになり、公開中の料金も外れました。取得された公開ページの中に中間の制作がなく、階層をたどれなくなるためです。料金自身が下書きになったわけではありません。

同じ状態でも、料金の直接の親ID113をWP_Queryのpost_parentへ指定すると、料金を取得できました。この例は、親自体の公開状態を取得条件にしていません。親が下書きなら子も出さない、といったサイト独自の要件がある場合は、親の状態も別に判定します。

自分のサイトでも、次の条件を確認すると、IDの誤りと階層・公開状態の違いを切り分けやすくなります。

確認する条件このサンプルの結果
サービスの直下・公開済み保守→制作
サービスの全子孫・公開済み保守→制作→料金
制作を下書きにした全子孫保守だけ。公開中の料金も外れる
制作を親IDにしたWP_Query制作が下書きでも公開中の料金を取得
保守と制作の順序を同じ10にするタイトル順で保守→制作
対象の子がいない有効な親ID0件メッセージ

引数の仕様と、コードを置く前提を確認する

取得範囲を変更するときは、WP_Queryのpost_parent・orderbyと、get_pages()のparent・child_ofを公式資料で確認してください。子孫をたどる処理はget_page_children()の資料でも確認できます。

仕様を確認

公式ドキュメント

PHPを使う方法自体をまだ決めていない場合は、標準の投稿一覧やブロックとの使い分けから確認できます。テンプレートへコードを置く段階では、本番前の検証手順も併せて確認してください。

公開前には取得件数だけでなく、親子関係を変えた場合と一覧の後に続く表示も確認します。

公開前の確認

  • 親IDと子ページ側の親設定が一致している
  • 直下の子と孫以下を含む子孫のどちらが必要か決まっている
  • 下書き・非公開・0件と並び順を試した
  • 一覧の後のタイトルとリンク先が正しい

この記事を書いた人

Huddly

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

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

この記事の検証情報

検証環境

WordPress
7.1
PHP
8.4.20
確認範囲
2026-11-04 時点に分離したローカルWordPress 7.1 / PHP 8.4.20で親子・公開状態・並び順・0件・一覧後の復元を実行で確認

更新履歴

  • 2026年11月5日 初回公開

この記事を活用する

あとで見返す・共有する

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