直下の子と、孫以下を含む子孫で取得方法を分ける
直下の子ページだけなら、WP_Queryでpost_typeをpage、post_parentを親ページのIDにします。孫以下も含めたい場合は、get_pages()のchild_ofを使えます。親のIDと公開状態を確認してから、必要な範囲に合う方法を選びましょう。URLの階層だけでは、固定ページの親子関係は判断できません。
子だけか、孫以下も含めるかを決める
「サービス」の下に「制作」と「保守」があり、制作の下に「料金」がある例を考えます。サービスの直下だけなら制作と保守、孫以下も含むなら料金も取得対象です。サービス自身はどちらの一覧にも含めません。
必要な範囲と引数の対応は、次のように分かれます。
| 表示したい範囲 | 取得方法 | 親IDを入れる引数 |
|---|---|---|
| 直下の子だけ | WP_Query | post_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です。

サンプルの親はサービス(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へ、そのまま貼るコードではありません。
<?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へ、そのまま貼るコードではありません。
<?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を表示できています。

公開済みだけを対象にした結果です。直下は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にする | タイトル順で保守→制作 |
| 対象の子がいない有効な親ID | 0件メッセージ |
引数の仕様と、コードを置く前提を確認する
取得範囲を変更するときは、WP_Queryのpost_parent・orderbyと、get_pages()のparent・child_ofを公式資料で確認してください。子孫をたどる処理はget_page_children()の資料でも確認できます。
仕様を確認
公式ドキュメント
PHPを使う方法自体をまだ決めていない場合は、標準の投稿一覧やブロックとの使い分けから確認できます。テンプレートへコードを置く段階では、本番前の検証手順も併せて確認してください。
公開前には取得件数だけでなく、親子関係を変えた場合と一覧の後に続く表示も確認します。
公開前の確認
- 親IDと子ページ側の親設定が一致している
- 直下の子と孫以下を含む子孫のどちらが必要か決まっている
- 下書き・非公開・0件と並び順を試した
- 一覧の後のタイトルとリンク先が正しい
この記事の検証情報
検証環境
- WordPress
- 7.1
- PHP
- 8.4.20
- 確認範囲
- 2026-11-04 時点に分離したローカルWordPress 7.1 / PHP 8.4.20で親子・公開状態・並び順・0件・一覧後の復元を実行で確認
更新履歴
- 2026年11月5日 初回公開
この記事を活用する
あとで見返す・共有する
お気に入りに保存できます。役に立ったポイントは、いいねや共有で教えてください。



