WordPressカスタムフィールド完全ガイド【標準機能・ACF対応】

WordPressカスタムフィールド完全ガイド【標準機能・ACF対応】

WordPressで「商品の価格を表示したい」「営業時間を管理したい」「FAQを効率的に更新したい」といった場面で威力を発揮するのがカスタムフィールドです。標準の投稿機能では表現しきれない、サイト独自の情報を構造化して管理できる強力な機能です。 このガイドでは、WordPress標準のカスタムフィールドから人気プラグインACFまで、初心者から実務レベルまで段階的に解説します。「どちらを選ぶべきか」「どう実装するか」「なぜ動かないのか」といった疑問を、豊富なコード例とトラブルシューティングで解決します。

目次

WordPressで「商品の価格を表示したい」「営業時間を管理したい」「FAQを効率的に更新したい」といった場面で威力を発揮するのがカスタムフィールドです。標準の投稿機能では表現しきれない、サイト独自の情報を構造化して管理できる強力な機能です。

このガイドでは、WordPress標準のカスタムフィールドから人気プラグインACFまで、初心者から実務レベルまで段階的に解説します。「どちらを選ぶべきか」「どう実装するか」「なぜ動かないのか」といった疑問を、豊富なコード例とトラブルシューティングで解決します。

こんな方におすすめ:

  • 企業サイトで商品情報や店舗情報を効率的に管理したい
  • ブログに星評価やおすすめ度などの項目を追加したい
  • クライアントが簡単に更新できる仕組みを作りたい
  • プラグインに依存しない軽量なソリューションを知りたい

前提条件:

  • WordPressの基本操作ができる
  • 簡単なPHPコードが理解できる
  • テンプレートファイルの編集経験がある

カスタムフィールドとは?(概念/投稿・固定ページとの違い/標準機能とACFの違い)

カスタムフィールドは、WordPressの標準的な投稿項目(タイトル、本文、アイキャッチ画像など)に加えて、独自の入力項目を追加する機能です。例えば、商品ページなら「価格」「在庫数」「仕様」、店舗情報なら「営業時間」「電話番号」「アクセス」など、コンテンツタイプに応じた専用項目を作成できます。

標準項目との違い:

項目標準項目カスタムフィールド
項目数固定(タイトル・本文・アイキャッチなど)無制限で追加可能
データ型テキスト・画像が中心テキスト・数値・日付・画像・ファイルなど
入力UI固定デザインカスタマイズ可能
表示制御全投稿タイプで共通投稿タイプ・テンプレート別に制御可能
管理の複雑さシンプル設計が重要

標準機能とACFプラグインの違い:

機能WordPress標準ACF(Advanced Custom Fields)
基本機能テキスト・テキストエリアのみ20種類以上のフィールドタイプ
UI/UXシンプルなキー・値入力直感的なGUI、リピーター対応
学習コスト低い(HTMLの知識程度)中程度(ACF独自の概念)
カスタマイズ性制限あり高度なカスタマイズ可能
パフォーマンス軽量プラグイン分重い
ライセンス無料無料版+有料版(Pro)
長期サポートWordPress本体に依存プラグイン開発チームに依存

用語整理と使いどころ(軽量=標準/多機能=ACF)

WordPress標準のカスタムフィールドを選ぶべき場面:

  • シンプルなテキスト情報の追加(価格、外部URL、説明文など)
  • パフォーマンスを最優先したい場合
  • プラグインの依存を避けたい長期運用サイト
  • 開発者がPHPに慣れており、カスタマイズを自前で行える場合

ACFを選ぶべき場面:

  • 複雑なデータ構造(リピーター、グループ、条件分岐)が必要
  • 非技術者が運用する場合(直感的なUI)
  • 画像・ファイル・日付選択など高機能なフィールドが必要
  • 開発速度を重視する場合

用語整理:

  • メタキー(Key):データベース上での項目名(例:product_price
  • メタ値(Value):実際に入力されたデータ(例:1980
  • フィールドグループ:ACFでの設定単位(関連するフィールドをまとめたもの)
  • 場所ルール:どの投稿・ページでフィールドを表示するかの条件設定

【基本】標準機能でカスタムフィールドを使う

編集画面での表示有効化(ブロックエディタの設定→パネルで「カスタムフィールド」をオン)

標準のカスタムフィールドは、デフォルトでは投稿編集画面に表示されません。以下の手順で有効化します:

手順:

  1. 投稿編集画面を開く
  2. 右上の「設定」アイコン(⚙️)をクリック
  3. 「パネル」タブを選択
  4. 「カスタムフィールド」のチェックボックスをオン
  5. 画面下部に「カスタムフィールド」セクションが表示される

初回利用時の注意点:

  • カスタムフィールドセクションは、投稿に何らかのカスタムフィールドが存在する場合のみ表示されます
  • 初回は以下のコードで空のカスタムフィールドを追加すると表示されます:
// functions.php に追加(初回のみ)
function add_custom_field_to_new_posts($post_id) {
    if (get_post_type($post_id) == 'post' && !get_post_meta($post_id, '_custom_field_initialized', true)) {
        add_post_meta($post_id, 'sample_field', '', true);
        add_post_meta($post_id, '_custom_field_initialized', '1', true);
    }
}
add_action('wp_insert_post', 'add_custom_field_to_new_posts');

Code language: PHP (php)

値の登録とテンプレート出力(get_post_meta()

管理画面での値の登録:

  1. 投稿編集画面の「カスタムフィールド」セクションで「新しいカスタムフィールドを追加」をクリック
  2. 名前:フィールドのキー名(例:product_price
  3. :実際のデータ(例:1980
  4. 「カスタムフィールドを追加」をクリック

テンプレートファイルでの出力:

<?php
// 基本的な取得・表示
$price = get_post_meta(get_the_ID(), 'product_price', true);
if ($price) {
    echo '<p>価格: ¥' . number_format($price) . '</p>';
}

// 複数の値を取得(第3引数をfalseまたは省略)
$features = get_post_meta(get_the_ID(), 'product_feature', false);
if ($features) {
    echo '<ul>';
    foreach ($features as $feature) {
        echo '<li>' . esc_html($feature) . '</li>';
    }
    echo '</ul>';
}

// 条件分岐での使用例
$sale_price = get_post_meta(get_the_ID(), 'sale_price', true);
$regular_price = get_post_meta(get_the_ID(), 'regular_price', true);

if ($sale_price && $sale_price < $regular_price) {
    echo '<span class="sale-price">セール価格: ¥' . number_format($sale_price) . '</span>';
    echo '<span class="regular-price">通常価格: ¥' . number_format($regular_price) . '</span>';
} else {
    echo '<span class="price">価格: ¥' . number_format($regular_price) . '</span>';
}
?>

Code language: HTML, XML (xml)

get_post_meta()関数の詳細:

  • 第1引数:投稿ID(get_the_ID()で現在の投稿IDを取得)
  • 第2引数:メタキー名(文字列)
  • 第3引数:単一値を取得するかどうか(true=単一値、false=配列)

成功確認(どのUI/URLを見ればOKか)

1. 管理画面での確認:

  • 投稿編集画面の「カスタムフィールド」セクションに入力した項目が表示される
  • 値を編集して「更新」後、再読み込みしても値が保持されている
  • 複数の投稿で同じキー名を使用すると、ドロップダウンで選択できるようになる

2. フロントエンドでの確認:

  • 該当する投稿ページ(https://yoursite.com/投稿スラッグ/)でカスタムフィールドの値が表示される
  • ブラウザの「検証」機能でHTMLソースを確認し、期待するHTMLが出力されている

3. データベースでの確認(上級者向け):

-- phpMyAdminやコマンドラインで確認
SELECT post_id, meta_key, meta_value 
FROM wp_postmeta 
WHERE post_id = 投稿ID 
AND meta_key = 'product_price';

Code language: JavaScript (javascript)

4. デバッグ用コード:

<?php
// テンプレートファイルの一時的なデバッグ用
$all_meta = get_post_meta(get_the_ID());
echo '<pre>';
print_r($all_meta);
echo '</pre>';
?>

Code language: HTML, XML (xml)

【応用】ACFプラグイン入門

インストールと有効化/フィールドグループ作成

ACFのインストール:

  1. WordPress管理画面で「プラグイン」→「新規追加」
  2. 「Advanced Custom Fields」を検索
  3. 「今すぐインストール」→「有効化」
  4. 左メニューに「カスタムフィールド」が追加される

フィールドグループの作成:

  1. 「カスタムフィールド」→「フィールドグループ」→「新規追加」
  2. グループタイトルを入力(例:「商品情報」)
  3. 「フィールドを追加」をクリック

基本的なフィールド設定:

フィールドラベル: 価格
フィールド名: product_price(自動生成)
フィールドタイプ: 数値
説明: 商品の販売価格を入力してください
必須: はい
デフォルト値: 0
Code language: HTTP (http)

場所ルールの設定:

  • 投稿タイプが次と等しい:投稿
  • または 投稿タイプが次と等しい:商品(カスタム投稿タイプの場合)
  • カテゴリーが次と等しい:商品(特定カテゴリーのみの場合)

主要フィールドタイプ(テキスト/画像/リピーター)と場所ルール

テキスト系フィールド:

【テキスト】
- 用途: 短いテキスト(商品名、価格など)
- 設定: 最大文字数、プレースホルダーテキスト
- 出力例: get_field('product_name')

【テキストエリア】
- 用途: 長いテキスト(説明文など)
- 設定: 行数、文字数制限
- 出力例: get_field('description')

【リッチテキストエディタ】
- 用途: HTML形式のコンテンツ
- 設定: ツールバー、メディアボタンの有無
- 出力例: the_field('rich_content') // HTMLとして出力
Code language: JavaScript (javascript)

画像・ファイル系フィールド:

【画像】
- 用途: 商品画像、ギャラリー画像など
- 返り値: 画像配列、画像URL、画像ID
- 設定例:
  返り値: 画像配列
  プレビューサイズ: 中
  ライブラリ: すべて

【ファイル】
- 用途: PDF、動画などのファイル
- 返り値: ファイル配列、ファイルURL、ファイルID
- 設定: MIME タイプ制限

選択系フィールド:

【選択】
- 用途: ドロップダウン選択(カテゴリー、ステータスなど)
- 選択肢: 値 : ラベル の形式
- 例: 
  new : 新品
  used : 中古品
  refurbished : リファビッシュ品

【チェックボックス】
- 用途: 複数選択可能な選択肢
- 選択肢設定: 同上
- 返り値: 配列

【ラジオボタン】
- 用途: 単一選択
- 選択肢設定: 同上
- その他オプション: 「その他」選択肢の有効化
Code language: JavaScript (javascript)

リピーター(Pro版のみ):

【リピーター】
- 用途: 繰り返し項目(FAQ、料金表、ギャラリーなど)
- サブフィールド: リピーター内の項目を定義
- 設定:
  最小値: 0
  最大値: 10
  レイアウト: テーブル/ブロック/行

成功確認(編集画面の表示・保存・プレビュー)

1. 編集画面での確認:

  • 投稿編集画面に設定したフィールドが表示される
  • フィールドタイプに応じた適切な入力UIが表示される(日付ピッカー、カラーピッカーなど)
  • 必須フィールドに未入力の場合、保存時にエラーメッセージが表示される

2. データ保存の確認:

  • 値を入力して「更新」をクリック
  • ページを再読み込みしても入力した値が保持されている
  • 「プレビュー」で一時保存された状態が確認できる

3. フィールドグループの設定確認:

  • カスタムフィールド→フィールドグループ→編集で設定を再確認
  • 場所ルールが正しく設定されているか確認
  • 不要な投稿タイプに表示されていないか確認

4. ACF独自の確認機能:

// functions.php でのデバッグ
function debug_acf_fields() {
    if (function_exists('get_fields')) {
        $fields = get_fields();
        if ($fields) {
            echo '<pre>';
            foreach ($fields as $name => $value) {
                echo $name . ': ';
                print_r($value);
                echo "\n";
            }
            echo '</pre>';
        }
    }
}
// 一時的にテンプレートファイルで呼び出し
debug_acf_fields();

Code language: PHP (php)

実務で使えるACF活用例

テンプレ出力(the_field()get_field())と「なぜ」

the_field()get_field() の使い分け:

<?php
// the_field() - 直接出力(echoが不要)
echo '<p>商品名: ';
the_field('product_name'); // 値をそのまま出力
echo '</p>';

// get_field() - 値を取得して加工後出力
$price = get_field('product_price');
if ($price) {
    echo '<p>価格: ¥' . number_format($price) . '</p>';
}

// 条件分岐での使用
$discount_rate = get_field('discount_rate');
if ($discount_rate > 0) {
    echo '<span class="discount">割引率: ' . $discount_rate . '%</span>';
}
?>

Code language: HTML, XML (xml)

なぜ使い分けが必要なのか:

  1. データの加工が必要な場合: get_field() を使用
    • 数値のフォーマット(カンマ区切りなど)
    • 条件分岐での表示制御
    • 複数フィールドとの組み合わせ
  2. そのまま出力したい場合: the_field() を使用
    • シンプルなテキスト
    • HTMLを含むリッチテキスト
    • コードが短くて済む
  3. セキュリティ面での考慮:
// ユーザー入力値は必ずエスケープ
$user_input = get_field('user_comment');
echo '<p>' . esc_html($user_input) . '</p>';

// HTMLを許可する場合(管理者入力のみ)
the_field('admin_message'); // リッチテキストエディタの場合

Code language: PHP (php)

リピーター(have_rows()the_row())のループと注意点

基本的なリピーターの出力:

<?php if (have_rows('faq_items')): ?>
    <div class="faq-section">
        <h2>よくある質問</h2>
        <?php while (have_rows('faq_items')): the_row(); ?>
            <div class="faq-item">
                <h3 class="faq-question"><?php the_sub_field('question'); ?></h3>
                <div class="faq-answer">
                    <?php the_sub_field('answer'); ?>
                </div>
            </div>
        <?php endwhile; ?>
    </div>
<?php endif; ?>

Code language: HTML, XML (xml)

注意点とベストプラクティス:

<?php
// 注意点1: 空の値のチェック
if (have_rows('gallery_images')):
    echo '<div class="gallery">';
    while (have_rows('gallery_images')): the_row();
        $image = get_sub_field('image');
        $caption = get_sub_field('caption');
        
        // 画像が設定されている場合のみ出力
        if ($image): ?>
            <figure class="gallery-item">
                <img src="<?php echo esc_url($image['sizes']['medium']); ?>" 
                     alt="<?php echo esc_attr($image['alt']); ?>">
                <?php if ($caption): ?>
                    <figcaption><?php echo esc_html($caption); ?></figcaption>
                <?php endif; ?>
            </figure>
        <?php endif;
    endwhile;
    echo '</div>';
endif;

// 注意点2: ネストしたリピーターの場合
if (have_rows('sections')):
    while (have_rows('sections')): the_row();
        echo '<section>';
        the_sub_field('section_title');
        
        // ネストしたリピーター
        if (have_rows('section_items')):
            echo '<ul>';
            while (have_rows('section_items')): the_row();
                echo '<li>' . get_sub_field('item_text') . '</li>';
            endwhile;
            echo '</ul>';
        endif;
        
        echo '</section>';
    endwhile;
endif;

// 注意点3: パフォーマンスを考慮した取得
$rows = get_field('large_repeater');
if ($rows):
    // 一度に取得して配列として処理(大量データの場合)
    foreach ($rows as $row):
        echo '<div>' . esc_html($row['item_name']) . '</div>';
    endforeach;
endif;
?>

Code language: HTML, XML (xml)

具体例:店舗情報/料金表(リピーター)/FAQ(リピーター)

店舗情報の実装例:

<?php
// フィールドグループ「店舗情報」の設定例
/*
店舗名: store_name (テキスト)
郵便番号: postal_code (テキスト)
住所: address (テキストエリア)
電話番号: phone (テキスト)
営業時間: business_hours (リピーター)
  - 曜日: day_of_week (選択)
  - 開店時間: open_time (時間ピッカー)
  - 閉店時間: close_time (時間ピッカー)
  - 定休日: closed (真偽値)
アクセス方法: access_methods (リピーター)
  - 交通手段: transport (選択: 電車/バス/車)
  - 説明: description (テキストエリア)
*/

// single-store.php での出力
?>
<article class="store-info">
    <h1><?php the_field('store_name'); ?></h1>
    
    <div class="basic-info">
        <p><strong>住所:</strong><?php the_field('postal_code'); ?><br>
        <?php the_field('address'); ?></p>
        
        <p><strong>電話:</strong> 
        <a href="tel:<?php echo str_replace('-', '', get_field('phone')); ?>">
            <?php the_field('phone'); ?>
        </a></p>
    </div>
    
    <?php if (have_rows('business_hours')): ?>
        <div class="business-hours">
            <h2>営業時間</h2>
            <table>
                <thead>
                    <tr><th>曜日</th><th>営業時間</th></tr>
                </thead>
                <tbody>
                    <?php while (have_rows('business_hours')): the_row(); ?>
                        <tr>
                            <td><?php the_sub_field('day_of_week'); ?></td>
                            <td>
                                <?php if (get_sub_field('closed')): ?>
                                    定休日
                                <?php else: ?>
                                    <?php the_sub_field('open_time'); ?> - <?php the_sub_field('close_time'); ?>
                                <?php endif; ?>
                            </td>
                        </tr>
                    <?php endwhile; ?>
                </tbody>
            </table>
        </div>
    <?php endif; ?>
    
    <?php if (have_rows('access_methods')): ?>
        <div class="access-info">
            <h2>アクセス</h2>
            <?php while (have_rows('access_methods')): the_row(); ?>
                <div class="access-method">
                    <h3><?php the_sub_field('transport'); ?></h3>
                    <p><?php the_sub_field('description'); ?></p>
                </div>
            <?php endwhile; ?>
        </div>
    <?php endif; ?>
</article>

Code language: HTML, XML (xml)

料金表の実装例:

<?php
// フィールドグループ「料金表」
/*
料金プラン: pricing_plans (リピーター)
  - プラン名: plan_name (テキスト)
  - 料金: price (数値)
  - 期間単位: period (選択: 月額/年額/一回)
  - 特徴: features (リピーター)
    - 特徴項目: feature_item (テキスト)
    - 利用可能: available (真偽値)
  - おすすめプラン: recommended (真偽値)
  - 説明: description (テキストエリア)
*/
?>

<div class="pricing-table">
    <h2>料金プラン</h2>
    <?php if (have_rows('pricing_plans')): ?>
        <div class="plans-container">
            <?php while (have_rows('pricing_plans')): the_row(); ?>
                <div class="plan <?php echo get_sub_field('recommended') ? 'recommended' : ''; ?>">
                    <?php if (get_sub_field('recommended')): ?>
                        <div class="recommended-badge">おすすめ</div>
                    <?php endif; ?>
                    
                    <h3><?php the_sub_field('plan_name'); ?></h3>
                    
                    <div class="price">
                        <span class="amount">¥<?php echo number_format(get_sub_field('price')); ?></span>
                        <span class="period"><?php the_sub_field('period'); ?></span>
                    </div>
                    
                    <?php if (get_sub_field('description')): ?>
                        <p class="plan-description"><?php the_sub_field('description'); ?></p>
                    <?php endif; ?>
                    
                    <?php if (have_rows('features')): ?>
                        <ul class="features">
                            <?php while (have_rows('features')): the_row(); ?>
                                <li class="<?php echo get_sub_field('available') ? 'available' : 'unavailable'; ?>">
                                    <span class="icon"><?php echo get_sub_field('available') ? '✓' : '×'; ?></span>
                                    <?php the_sub_field('feature_item'); ?>
                                </li>
                            <?php endwhile; ?>
                        </ul>
                    <?php endif; ?>
                    
                    <button class="cta-button">お申込み</button>
                </div>
            <?php endwhile; ?>
        </div>
    <?php endif; ?>
</div>

<style>
.plans-container {
    display: flex;
    gap: 2rem;
    justify-content: center;
    flex-wrap: wrap;
}

.plan {
    border: 2px solid #ddd;
    border-radius: 8px;
    padding: 2rem;
    position: relative;
    flex: 1;
    min-width: 300px;
    text-align: center;
}

.plan.recommended {
    border-color: #007cba;
    transform: scale(1.05);
}

.recommended-badge {
    position: absolute;
    top: -10px;
    left: 50%;
    transform: translateX(-50%);
    background: #007cba;
    color: white;
    padding: 0.5rem 1rem;
    border-radius: 20px;
    font-size: 0.9rem;
}

.price .amount {
    font-size: 2rem;
    font-weight: bold;
    color: #007cba;
}

.features {
    list-style: none;
    padding: 0;
    margin: 1.5rem 0;
}

.features li {
    padding: 0.5rem 0;
    border-bottom: 1px solid #eee;
}

.features li.unavailable {
    color: #999;
    text-decoration: line-through;
}

.cta-button {
    background: #007cba;
    color: white;
    border: none;
    padding: 1rem 2rem;
    border-radius: 5px;
    font-size: 1.1rem;
    cursor: pointer;
    width: 100%;
}
</style>
Code language: HTML, XML (xml)

FAQ(よくある質問)の実装例:

<?php
// フィールドグループ「FAQ」
/*
FAQ項目: faq_items (リピーター)
  - カテゴリー: category (選択)
  - 質問: question (テキスト)
  - 回答: answer (リッチテキストエディタ)
  - 重要度: priority (選択: 高/中/低)
  - 表示順: display_order (数値)
*/
?>

<section class="faq-section">
    <h2>よくある質問</h2>
    
    <?php if (have_rows('faq_items')): ?>
        <!-- カテゴリーフィルター用のJavaScript -->
        <div class="faq-filters">
            <button class="filter-btn active" data-category="all">すべて</button>
            <?php
            // カテゴリーの一覧を取得
            $categories = array();
            if (have_rows('faq_items')):
                while (have_rows('faq_items')): the_row();
                    $cat = get_sub_field('category');
                    if ($cat && !in_array($cat, $categories)) {
                        $categories[] = $cat;
                    }
                endwhile;
            endif;
            
            foreach ($categories as $category): ?>
                <button class="filter-btn" data-category="<?php echo esc_attr($category); ?>">
                    <?php echo esc_html($category); ?>
                </button>
            <?php endforeach; ?>
        </div>
        
        <div class="faq-items">
            <?php 
            // 表示順でソート
            $faq_rows = get_field('faq_items');
            if ($faq_rows) {
                usort($faq_rows, function($a, $b) {
                    return ($a['display_order'] ?? 999) - ($b['display_order'] ?? 999);
                });
                
                foreach ($faq_rows as $index => $row): ?>
                    <div class="faq-item" data-category="<?php echo esc_attr($row['category']); ?>">
                        <div class="faq-question" data-priority="<?php echo esc_attr($row['priority']); ?>">
                            <h3>
                                <span class="question-icon">Q</span>
                                <?php echo esc_html($row['question']); ?>

Code language: HTML, XML (xml)

トラブルシューティングとQ&A

症状→原因→直し方(5件以上:値が出ない/画像の返り値/フィールド名とキー混同/WP_DEBUG/表示条件ミス など)

Q1: カスタムフィールドの値が表示されない

症状: get_field()get_post_meta() で値を取得しようとしても空になる

原因1: フィールド名(キー)が間違っている

php

<em>// ❌ 間違い:ACFのフィールドラベルを使用</em>
$price = get_field('価格');

<em>// ⭕ 正解:フィールド名を使用</em>
$price = get_field('product_price');Code language: PHP (php)

原因2: 投稿IDが取得できていない

php

<em>// ❌ 間違い:ループ外で get_the_ID() を使用</em>
$price = get_field('product_price', get_the_ID());

<em>// ⭕ 正解:明示的に投稿IDを指定</em>
global $post;
$price = get_field('product_price', $post->ID);

<em>// または、ループ内で使用</em>
if (have_posts()) : while (have_posts()) : the_post();
    $price = get_field('product_price'); <em>// 投稿ID省略可能</em>
endwhile; endif;Code language: PHP (php)

解決方法:

php

<em>// デバッグ用:すべてのカスタムフィールドを確認</em>
function debug_custom_fields() {
    global $post;
    if (function_exists('get_fields')) {
        <em>// ACFの場合</em>
        $fields = get_fields($post->ID);
        echo '<h3>ACF Fields:</h3><pre>';
        print_r($fields);
        echo '</pre>';
    }
    
    <em>// 標準カスタムフィールドの場合</em>
    $meta = get_post_meta($post->ID);
    echo '<h3>Standard Meta:</h3><pre>';
    print_r($meta);
    echo '</pre>';
}
<em>// テンプレートで一時的に実行</em>
debug_custom_fields();Code language: PHP (php)

Q2: 画像フィールドが正しく表示されない

症状: 画像フィールドの値は取得できるが、<img> タグとして表示されない

原因: ACFの画像フィールドの返り値設定を理解していない

ACFの画像返り値タイプ別の出力方法:

php

<?php
$image = get_field('product_image');

if ($image):
    <em>// 返り値: 画像配列の場合</em>
    if (is_array($image)): ?>
        <img src="<?php echo esc_url($image['sizes']['medium']); ?>" 
             alt="<?php echo esc_attr($image['alt']); ?>">
        
    <?php <em>// 返り値: 画像URLの場合</em>
    elseif (is_string($image) && filter_var($image, FILTER_VALIDATE_URL)): ?>
        <img src="<?php echo esc_url($image); ?>" alt="">
        
    <?php <em>// 返り値: 画像IDの場合</em>
    elseif (is_numeric($image)):
        $image_url = wp_get_attachment_image_src($image, 'medium');
        if ($image_url): ?>
            <img src="<?php echo esc_url($image_url[0]); ?>" alt="">
        <?php endif;
    endif;
endif;
?>Code language: HTML, XML (xml)

推奨設定と出力方法:

php

<em>// ACF設定: 返り値 = 画像配列(推奨)</em>
$image = get_field('hero_image');
if ($image):
    <em>// レスポンシブ対応の出力</em>
    echo wp_get_attachment_image($image['ID'], 'large', false, [
        'class' => 'hero-image',
        'alt' => $image['alt'] ?: get_the_title()
    ]);
endif;Code language: PHP (php)

Q3: フィールド名とキーを混同している

症状: ACFでフィールドを設定したが、get_field()で取得できない

原因: フィールドラベルとフィールド名の違いを理解していない

ACFの項目説明:

  • フィールドラベル: 管理画面で表示される名前(日本語可)
  • フィールド名: データベースに保存されるキー(英数字・アンダースコア)
  • フィールドキー: ACF内部で使用するユニークID(field_xxxxx

php

<?php
<em>/*
</em>ACF設定例:
フィールドラベル: 商品価格
フィールド名: product_price (自動生成または手動設定)
フィールドキー: field_66a1b2c3d4e5f (自動生成)
<em>*/</em>

<em>// ❌ 間違い:フィールドラベルを使用</em>
$price = get_field('商品価格');

<em>// ⭕ 正解:フィールド名を使用</em>
$price = get_field('product_price');

<em>// 上級者向け:フィールドキーでも取得可能(推奨しない)</em>
$price = get_field('field_66a1b2c3d4e5f');
?>Code language: HTML, XML (xml)

確認方法:

  1. ACFの「フィールドグループ」編集画面でフィールド名を確認
  2. 開発者ツールでHTMLのname属性を確認
  3. データベースのwp_postmetaテーブルでmeta_keyを確認

Q4: WP_DEBUGでエラーが出る

症状: WP_DEBUGを有効にするとPHPエラーやワーニングが表示される

よくあるエラーと対処法:

php

<?php
<em>// エラー1: Undefined index</em>
<em>// ❌ エラーの出るコード</em>
echo $_POST['custom_field'];

<em>// ⭕ 修正版</em>
echo isset($_POST['custom_field']) ? sanitize_text_field($_POST['custom_field']) : '';

<em>// エラー2: Trying to get property of non-object</em>
<em>// ❌ エラーの出るコード</em>
global $post;
$meta = get_post_meta($post->ID, 'custom_field', true);

<em>// ⭕ 修正版</em>
global $post;
if ($post && is_object($post)) {
    $meta = get_post_meta($post->ID, 'custom_field', true);
}

<em>// エラー3: ACF関数の未定義チェック不足</em>
<em>// ❌ エラーの出るコード</em>
$value = get_field('my_field');

<em>// ⭕ 修正版</em>
if (function_exists('get_field')) {
    $value = get_field('my_field');
} else {
    $value = get_post_meta(get_the_ID(), 'my_field', true);
}

<em>// エラー4: 配列の空チェック不足</em>
<em>// ❌ エラーの出るコード</em>
$gallery = get_field('gallery');
foreach ($gallery as $image) {
    echo wp_get_attachment_image($image['ID']);
}

<em>// ⭕ 修正版</em>
$gallery = get_field('gallery');
if ($gallery && is_array($gallery)) {
    foreach ($gallery as $image) {
        if (isset($image['ID'])) {
            echo wp_get_attachment_image($image['ID']);
        }
    }
}
?>Code language: HTML, XML (xml)

デバッグ用の汎用関数:

php

function safe_get_field($field_name, $post_id = null, $default = '') {
    if (function_exists('get_field')) {
        $value = get_field($field_name, $post_id);
        return $value !== false ? $value : $default;
    } else {
        $post_id = $post_id ?: get_the_ID();
        return get_post_meta($post_id, $field_name, true) ?: $default;
    }
}

<em>// 使用例</em>
$price = safe_get_field('product_price', null, 0);
echo $price ? '¥' . number_format($price) : '価格未設定';Code language: PHP (php)

Q5: 表示条件の設定ミス(ACF)

症状: フィールドグループを作成したが、編集画面に表示されない

原因: 場所ルールの設定が間違っている

よくある設定ミスと正しい設定:

php

<em>// ケース1: 特定の投稿タイプのみで表示したい</em>
<em>// ❌ 間違い:「投稿」を選択</em>
<em>// ⭕ 正解:「投稿タイプ」=「product」(カスタム投稿タイプの場合)</em>

<em>// ケース2: 特定のページテンプレートでのみ表示したい</em>
<em>// ❌ 間違い:「ページ」=「特定ページID」</em>
<em>// ⭕ 正解:「ページテンプレート」=「page-products.php」</em>

<em>// ケース3: 管理者のみに表示したい</em>
<em>// ❌ 間違い:場所ルールでは制御不可</em>
<em>// ⭕ 正解:functions.phpで制御</em>
function hide_acf_from_non_admins() {
    if (!current_user_can('administrator')) {
        add_filter('acf/get_field_groups', function($field_groups) {
            return array_filter($field_groups, function($group) {
                return $group['key'] !== 'group_admin_only_fields';
            });
        });
    }
}
add_action('acf/init', 'hide_acf_from_non_admins');

<em>// ケース4: 条件付きフィールドが表示されない</em>
<em>// フィールド設定の「条件付きロジック」を確認</em>
<em>// 依存するフィールドの値が正確に設定されているか確認</em>Code language: JavaScript (javascript)

場所ルールのデバッグ方法:

php

<em>// 現在のページ情報を確認する関数</em>
function debug_page_info() {
    if (is_admin()) return;
    
    global $post;
    echo '<div style="background: #f0f0f0; padding: 1rem; margin: 1rem 0;">';
    echo '<h4>Page Debug Info:</h4>';
    echo 'Post Type: ' . get_post_type() . '<br>';
    echo 'Post ID: ' . get_the_ID() . '<br>';
    echo 'Template: ' . get_page_template_slug() . '<br>';
    echo 'Is Front Page: ' . (is_front_page() ? 'Yes' : 'No') . '<br>';
    echo 'Is Page: ' . (is_page() ? 'Yes' : 'No') . '<br>';
    echo '</div>';
}
<em>// 一時的にテンプレートで実行</em>
debug_page_info();Code language: PHP (php)

Q6: リピーターフィールドで無限ループが発生

症状: リピーターフィールドの出力でページが固まる、またはメモリエラーが発生

原因: ネストしたリピーターの処理が不適切

php

<?php
<em>// ❌ 危険:無限ループの可能性</em>
while (have_rows('parent_repeater')):
    the_row();
    while (have_rows('child_repeater')):
        the_row();
        <em>// リピーターの状態がリセットされない場合がある</em>
    endwhile;
endwhile;

<em>// ⭕ 安全:配列として取得して処理</em>
$parent_rows = get_field('parent_repeater');
if ($parent_rows):
    foreach ($parent_rows as $parent_row):
        echo '<h3>' . esc_html($parent_row['parent_title']) . '</h3>';
        
        if (isset($parent_row['child_repeater']) && is_array($parent_row['child_repeater'])):
            foreach ($parent_row['child_repeater'] as $child_row):
                echo '<p>' . esc_html($child_row['child_content']) . '</p>';
            endforeach;
        endif;
    endforeach;
endif;

<em>// さらに安全:行数制限付き</em>
$parent_rows = get_field('parent_repeater');
if ($parent_rows && count($parent_rows) <= 50): <em>// 最大50行まで</em>
    foreach (array_slice($parent_rows, 0, 50) as $parent_row):
        <em>// 処理</em>
    endforeach;
endif;
?>Code language: HTML, XML (xml)

Q7: カスタムフィールドがREST APIで取得できない

症状: フロントエンドJavaScriptでカスタムフィールドを取得しようとしても値が返らない

原因: REST APIでの公開設定がされていない

php

<em>// ACFの場合:フィールド設定で「REST APIで表示」をオンに</em>

<em>// 標準カスタムフィールドの場合:functions.phpで設定</em>
function expose_custom_fields_in_rest() {
    register_rest_field('post', 'product_price', array(
        'get_callback' => function($post) {
            return get_post_meta($post['id'], 'product_price', true);
        },
        'update_callback' => function($value, $post) {
            return update_post_meta($post->ID, 'product_price', sanitize_text_field($value));
        },
        'schema' => array(
            'description' => '商品価格',
            'type' => 'string'
        )
    ));
}
add_action('rest_api_init', 'expose_custom_fields_in_rest');

<em>// 使用例(JavaScript)</em>
fetch('/wp-json/wp/v2/posts/123')
    .then(response => response.json())
    .then(data => {
        console.log('Price:', data.product_price);
    });Code language: PHP (php)

仕上げチェックリスト

機能/表示/SEO/パフォーマンス

基本機能チェック

  • 管理画面でカスタムフィールドの入力欄が表示される
  • 値を入力・保存できる
  • フロントエンドで値が正しく表示される
  • 空の値の場合にエラーが出ない
  • 必須フィールドの場合、未入力時に適切な警告が表示される

データの整合性チェック

  • 数値フィールドに文字列を入力した場合の処理が適切
  • 画像フィールドで画像が削除された場合の処理が適切
  • リピーターフィールドの順序変更が正常に動作する
  • 複数言語サイトの場合、言語ごとに値が保存される

表示・UI/UXチェック

  • レスポンシブデザインで正常に表示される
  • 画像フィールドのalt属性が適切に設定される
  • フォームの入力値検証が適切に動作する
  • アクセシビリティ(タブ移動、スクリーンリーダー対応)が考慮されている

SEO関連チェック

  • 構造化データ(JSON-LD)が適切に出力される
  • メタディスクリプションにカスタムフィールドの内容が反映される(必要に応じて)
  • 画像のファイル名・alt属性がSEOに配慮されている
  • 重複コンテンツが発生していない

セキュリティチェック

  • ユーザー入力値が適切にサニタイズ・エスケープされている
  • 権限制御が正しく設定されている(管理者のみ編集可能など)
  • XSS攻撃に対する対策が講じられている
  • SQLインジェクション対策が適切に実装されている

php

<em>// セキュリティチェック用のサンプルコード</em>
function secure_custom_field_output($field_value, $field_type = 'text') {
    if (empty($field_value)) return '';
    
    switch ($field_type) {
        case 'text':
            return esc_html($field_value);
        case 'url':
            return esc_url($field_value);
        case 'html':
            return wp_kses_post($field_value);
        case 'number':
            return is_numeric($field_value) ? intval($field_value) : 0;
        default:
            return esc_html($field_value);
    }
}

<em>// 使用例</em>
$price = get_field('product_price');
echo secure_custom_field_output($price, 'number');
Code language: PHP (php)

パフォーマンスチェック

  • 大量のカスタムフィールドでページ表示速度が遅くならない
  • データベースクエリが最適化されている
  • 画像フィールドで適切なサイズが使用されている
  • 不要なACFプラグインの機能が無効化されている(Pro版の場合)

php

<em>// パフォーマンス最適化の例</em>
function optimize_acf_queries() {
    <em>// 必要なフィールドのみを一括取得</em>
    $fields = get_fields(); <em>// 全フィールドを一度に取得</em>
    
    if ($fields) {
        $price = $fields['product_price'] ?? '';
        $description = $fields['product_description'] ?? '';
        <em>// 個別に get_field() を呼び出さない</em>
    }
}
Code language: PHP (php)

バックアップ・復旧チェック

  • カスタムフィールドの設定がエクスポート可能
  • データベースのバックアップにカスタムフィールドが含まれる
  • 本番環境とステージング環境でデータが同期できる
  • プラグインを無効化してもデータが失われない(可能な限り)

ブラウザ・環境互換性チェック

  • 主要ブラウザ(Chrome、Firefox、Safari、Edge)で正常動作
  • モバイルブラウザで正常動作
  • WordPressの最新版で動作確認済み
  • 使用中のテーマとプラグインとの競合がない

まとめ(学べたこと/次の一歩)

このガイドを通じて、WordPressのカスタムフィールドについて、基礎から実務レベルまで幅広い知識を身につけることができました。

学べたこと:

基礎知識

  • カスタムフィールドの概念と標準機能・ACFの違い
  • get_post_meta()get_field()の使い分け
  • フィールドタイプ別の適切な出力方法

実装スキル

  • 標準機能での基本的なカスタムフィールド作成
  • ACFを使った高度なフィールド設計
  • リピーターフィールドの効果的な活用方法
  • セキュアなデータの取得・表示方法

実務ノウハウ

  • 店舗情報、料金表、FAQなどの具体的な実装パターン
  • よくあるエラーの原因と解決方法
  • パフォーマンスとセキュリティを考慮した実装
  • 保守性の高いコード設計

次の一歩:

1. 高度なカスタマイズ

php

// カスタムメタボックスの作成
function add_custom_meta_boxes() {
    add_meta_box(
        'product_details',
        '商品詳細',
        'product_details_callback',
        'product',
        'normal',
        'high'
    );
}

// 条件付きフィールドの実装
function conditional_fields_script() {
    ?>
    <script>
    jQuery(document).ready(function($) {
        $('#product_type').change(function() {
            if ($(this).val() === 'digital') {
                $('.download-fields').show();
                $('.shipping-fields').hide();
            } else {
                $('.download-fields').hide();
                $('.shipping-fields').show();
            }
        });
    });
    </script>
    <?php
}
Code language: JavaScript (javascript)

2. REST API・ヘッドレスCMS連携

javascript

<em>// Next.jsでのカスタムフィールド取得例</em>
const getProductData = async (id) => {
    const response = await fetch(`/wp-json/wp/v2/products/${id}`);
    const product = await response.json();
    
    return {
        name: product.title.rendered,
        price: product.acf.product_price,
        images: product.acf.gallery,
        description: product.acf.description
    };
};
Code language: JavaScript (javascript)

3. 大規模サイトでの最適化

  • カスタムフィールドのインデックス設計
  • キャッシュ戦略の実装
  • データベースクエリの最適化
  • マルチサイト環境での設計

4. 運用・保守の自動化

  • カスタムフィールドの一括更新スクリプト
  • データ移行ツールの作成
  • 品質チェック自動化
  • 継続的インテグレーション(CI/CD)の構築

カスタムフィールドをマスターすることで、WordPressサイトの表現力と管理性を大幅に向上させることができます。クライアントのニーズに応じた柔軟なコンテンツ管理システムを構築し、長期的に運用しやすいサイトを作っていきましょう。

お問い合わせ

    この記事も読まれています
    記事一覧へ