2026年9月24日
2026年9月24日
WordPressのACFリピーターフィールドを使ったリスト表示の実装|have_rowsの基本からネスト・ブロックテーマ対応まで
ACF のリピーターフィールドで作ったリストは、テンプレートで have_rows() と the_row() のループを回し、get_sub_field() で各行の値を取り出して、エスケープしてから出力します。この記事では、フィールドの設計から、基本のリスト表示、画像やリンクを含む行、件数の制限、入れ子のリピーター、ブロックテーマでの表示方法、つまずきやすい点までを説明します。
前提:リピーターは ACF PRO の機能
リピーターフィールドは ACF PRO で使える機能で、無料版の ACF には含まれていません。同じく PRO の機能として、ギャラリーやフレキシブルコンテンツ、オプションページがあります。無料版でフィールドの種類に「リピーター」が出てこないのはこのためです。
なお、ACF をもとにした派生プラグイン(WordPress.org で配布されている Secure Custom Fields など)では、収録されている機能や関数の扱いが ACF と異なる場合があります。どちらを使っているかを管理画面のプラグイン一覧で確認し、それぞれの公式ドキュメントに当たってください(2026年10月現在)。
作業前にバックアップを取り、テンプレートの編集は子テーマで行います。親テーマのファイルを直接編集すると、テーマの更新で消えてしまいます。
手順1:フィールドを設計する
例として、固定ページに「よくある質問」のリストを作ります。フィールドグループに次のように登録します。
- リピーター:フィールド名
faq- サブフィールド:テキスト
question(質問) - サブフィールド:テキストエリア
answer(回答)
- サブフィールド:テキスト
フィールド名(faq、question など)は、テンプレートから値を取り出すときのキーになります。ラベルはあとから変えても問題ありませんが、フィールド名を変えると既存のデータが読めなくなるので、最初に決めておきましょう。
リピーターの設定では、最小・最大の行数や、管理画面での表示形式(表・ブロック・行)を選べます。行数が多くなる場合は、管理画面でページ送りする設定が用意されているバージョンもあります。
手順2:基本のリスト表示
子テーマの固定ページ用テンプレート(page.php やカスタムテンプレート)に、次のように書きます。
<?php if ( have_rows( 'faq' ) ) : ?>
<dl class="faq-list">
<?php while ( have_rows( 'faq' ) ) : the_row(); ?>
<?php
$question = get_sub_field( 'question' );
$answer = get_sub_field( 'answer' );
if ( ! $question ) {
continue; // 質問が空の行は飛ばす
}
?>
<dt><?php echo esc_html( $question ); ?></dt>
<dd><?php echo nl2br( esc_html( $answer ) ); ?></dd>
<?php endwhile; ?>
</dl>
<?php endif; ?>
have_rows( 'faq' )は、行が残っているかを返します。最初のifで「1行も無ければリスト自体を出さない」ようにしていますthe_row()を呼ぶと次の行に進みます。これを忘れると同じ行を見続けて無限ループになりますget_sub_field()は値を返すだけなので、出力側でesc_html()を通します。the_sub_field()で直接出力することもできますが、近年の ACF では出力時の HTML の扱い(安全化処理)が変更されてきたため、取り出してから自分でエスケープする書き方のほうが挙動を把握しやすくなります- 改行を活かしたいテキストエリアは、
esc_html()のあとにnl2br()を通しています。順番を逆にすると<br>までエスケープされてしまいます
ほかの投稿の値を表示したいときは have_rows( 'faq', $post_id )、オプションページの値なら have_rows( 'faq', 'option' ) のように2番目の引数で取得元を指定します。
手順3:画像やリンクを含む行
スタッフ紹介のように、画像やリンクを含む行を表示する例です。画像サブフィールド photo の返り値の形式は「画像 ID」、リンクサブフィールド profile_link は「リンク配列」にしておきます。
<?php if ( have_rows( 'staff' ) ) : ?>
<ul class="staff-list">
<?php while ( have_rows( 'staff' ) ) : the_row(); ?>
<?php
$photo_id = get_sub_field( 'photo' );
$name = get_sub_field( 'name' );
$link = get_sub_field( 'profile_link' ); // url / title / target を持つ配列
?>
<li class="staff-item">
<?php
if ( $photo_id ) {
echo wp_get_attachment_image( $photo_id, 'medium', false, array( 'alt' => $name ) );
}
?>
<p class="staff-name"><?php echo esc_html( $name ); ?></p>
<?php if ( $link && ! empty( $link['url'] ) ) : ?>
<a href="<?php echo esc_url( $link['url'] ); ?>"
<?php echo ( '_blank' === $link['target'] ) ? 'target="_blank" rel="noopener"' : ''; ?>>
<?php echo esc_html( $link['title'] ? $link['title'] : 'プロフィールを見る' ); ?>
</a>
<?php endif; ?>
</li>
<?php endwhile; ?>
</ul>
<?php endif; ?>
画像を ID で受け取って wp_get_attachment_image() で出力すると、srcset や width・height 属性が付き、画面幅に応じた画像の出し分けとレイアウトのずれ防止に役立ちます。URL 形式で受け取って <img> を手書きするより扱いやすい方法です。
手順4:件数を制限する・行番号を使う
「先頭の3件だけ表示したい」場合は、get_field() で配列として受け取ると簡単です。リピーターの値は、各行がサブフィールド名をキーにした連想配列の配列で返ってきます。
<?php
$rows = get_field( 'faq' );
if ( $rows ) :
foreach ( array_slice( $rows, 0, 3 ) as $row ) :
?>
<p><?php echo esc_html( $row['question'] ); ?></p>
<?php
endforeach;
endif;
have_rows() のループを途中で break で抜けると、同じフィールドを同じページで再度ループしたときに途中から始まることがあります。件数を区切るなら配列で扱うほうが分かりやすく、ループを使い直す必要がある場合は reset_rows() で位置を戻せます。
ループの中で何行目かを知りたいときは get_row_index() が使えます(1から数えます)。番号付きの見出しや、アコーディオンの id 属性を作るのに便利です。
手順5:入れ子のリピーター
リピーターの中にリピーターを入れた場合も、内側で同じ形のループを書きます。例えば「料金プラン(plans)」の各行に「含まれる機能(features)」がある構成です。
<?php while ( have_rows( 'plans' ) ) : the_row(); ?>
<h3><?php echo esc_html( get_sub_field( 'plan_name' ) ); ?></h3>
<?php if ( have_rows( 'features' ) ) : ?>
<ul>
<?php while ( have_rows( 'features' ) ) : the_row(); ?>
<li><?php echo esc_html( get_sub_field( 'feature' ) ); ?></li>
<?php endwhile; ?>
</ul>
<?php endif; ?>
<?php endwhile; ?>
内側の have_rows() は、外側のループの現在の行を基準にサブフィールドを探します。外側のループの外で内側のフィールド名を呼んでも値は取れません。
ブロックテーマで表示する場合
ブロックテーマでは PHP のテンプレートファイルを使わないため、上のコードをそのまま書く場所がありません。主な選択肢は次の3つです。
- ショートコードにする:子テーマや mu-plugin で
add_shortcode()を使い、出力をob_start()とob_get_clean()で文字列にして返す。ショートコードブロックから呼び出せる - ACF Blocks を使う:ACF PRO の機能で、
block.jsonにブロックを定義し、表示用の PHP テンプレートを割り当てる - 独自ブロックを作る:
register_block_type()で動的ブロックを登録し、render_callbackやテンプレートで出力する
どの方法でも、表示部分のコードは手順2〜5と同じ考え方で書けます。
つまずきやすい点
- 何も表示されない:フィールド名(ラベルではなく名前)の綴り、フィールドグループの表示条件(どの投稿タイプ・テンプレートに出すか)、値を保存した投稿を見ているかを順に確認します
- 無限ループで画面が止まる:
whileの中でthe_row()を呼んでいない - ほかの投稿の値が出る:サブクエリの中で使っていて、取得元の投稿 ID がずれている。
have_rows( 'faq', $post_id )で明示します - サブフィールドの値で投稿を絞り込みたい:リピーターの値は
faq_0_questionのような連番付きのメタキーで保存されるため、meta_queryで扱いにくく、件数が多いと遅くなります。検索や絞り込みが主目的なら、カスタム投稿タイプやタクソノミーで設計し直すことを検討してください
まとめ
- リピーターは ACF PRO の機能。派生プラグインを使っている場合はその公式情報を確認する
- 表示は
have_rows()・the_row()・get_sub_field()のループで行い、出力時にエスケープする - 画像は ID で受け取り
wp_get_attachment_image()で出力、リンクはesc_url()を通す - 件数制限は
get_field()の配列で扱い、入れ子は内側でも同じループを書く - ブロックテーマではショートコード・ACF Blocks・独自ブロックのいずれかで表示する
- テンプレートの編集は子テーマで行い、作業前にバックアップを取る