2026年6月26日

2026年6月26日

WordPressのメタボックスをカスタマイズする方法【add_meta_box】

はじめに

WordPressのカスタムメタボックスを使うと、投稿・ページの編集画面に独自のフォームフィールドを追加できます。商品の価格・在庫数、記事の関連リンク、著者のSNSアカウントなど、標準フィールドでは対応できない情報を管理できます。

症状・原因

カスタムメタボックスが必要なケース:

  • 投稿に追加情報(価格、外部リンク、評価)を管理したい
  • 特定の投稿タイプだけに専用フィールドを表示したい
  • ACFのような複雑なUI不要で軽量な実装が必要
  • フォーム送信の安全性(CSRF対策)を確保したい

解決手順

ステップ1:メタボックスを追加する

// functions.php またはカスタムプラグイン

add_action('add_meta_boxes', 'add_product_meta_boxes');
function add_product_meta_boxes(): void {
    add_meta_box(
        'product_details',          // メタボックスID(一意の文字列)
        '商品詳細情報',              // タイトル
        'render_product_meta_box',  // コールバック関数
        'product',                  // 表示する投稿タイプ(配列も可)
        'normal',                   // 位置: normal / side / advanced
        'high',                     // 優先度: high / core / default / low
        ['id' => 'product_details'] // コールバックに渡す追加引数
    );
    
    // 複数の投稿タイプに表示する場合
    add_meta_box(
        'seo_settings',
        'SEO設定',
        'render_seo_meta_box',
        ['post', 'page', 'product'],  // 複数指定
        'side',
        'high'
    );
}

ステップ2:メタボックスのUIを描画する

// 商品詳細メタボックスのレンダリング
function render_product_meta_box(WP_Post $post, array $metabox): void {
    // nonceフィールド(CSRF対策)
    wp_nonce_field('product_meta_nonce_action', 'product_meta_nonce');
    
    // 保存済みの値を取得
    $price    = get_post_meta($post->ID, '_product_price', true);
    $stock    = get_post_meta($post->ID, '_product_stock', true);
    $sku      = get_post_meta($post->ID, '_product_sku', true);
    $featured = get_post_meta($post->ID, '_product_featured', true);
    $notes    = get_post_meta($post->ID, '_product_notes', true);
    
    ?>
    <style>
        .product-meta-table { width:100%; border-collapse:collapse; }
        .product-meta-table th { text-align:left; padding:8px; width:140px; font-weight:600; }
        .product-meta-table td { padding:8px; }
        .product-meta-table input[type="text"],
        .product-meta-table input[type="number"],
        .product-meta-table textarea { width:100%; }
    </style>
    <table class="product-meta-table">
        <tr>
            <th><label for="product_price">価格(円)</label></th>
            <td>
                <input type="number" id="product_price" name="product_price"
                       value="<?php echo esc_attr($price); ?>"
                       min="0" step="1" placeholder="例: 3980">
            </td>
        </tr>
        <tr>
            <th><label for="product_stock">在庫数</label></th>
            <td>
                <input type="number" id="product_stock" name="product_stock"
                       value="<?php echo esc_attr($stock); ?>"
                       min="0" step="1">
            </td>
        </tr>
        <tr>
            <th><label for="product_sku">SKU</label></th>
            <td>
                <input type="text" id="product_sku" name="product_sku"
                       value="<?php echo esc_attr($sku); ?>"
                       placeholder="例: PROD-001">
            </td>
        </tr>
        <tr>
            <th>おすすめ商品</th>
            <td>
                <label>
                    <input type="checkbox" name="product_featured" value="1"
                           <?php checked($featured, '1'); ?>>
                    おすすめ商品として表示する
                </label>
            </td>
        </tr>
        <tr>
            <th><label for="product_notes">内部メモ</label></th>
            <td>
                <textarea id="product_notes" name="product_notes"
                          rows="3" placeholder="社内向けメモ(公開されません)"
                ><?php echo esc_textarea($notes); ?></textarea>
            </td>
        </tr>
    </table>
    <?php
}

ステップ3:メタデータを保存する

add_action('save_post', 'save_product_meta', 10, 2);
function save_product_meta(int $post_id, WP_Post $post): void {
    // 自動保存の場合はスキップ
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
    
    // nonce検証(CSRF対策)
    if (!isset($_POST['product_meta_nonce']) ||
        !wp_verify_nonce($_POST['product_meta_nonce'], 'product_meta_nonce_action')) {
        return;
    }
    
    // 権限確認
    if (!current_user_can('edit_post', $post_id)) return;
    
    // 対象の投稿タイプかを確認
    if ($post->post_type !== 'product') return;
    
    // データのサニタイズと保存
    $fields = [
        '_product_price'    => ['key' => 'product_price',    'sanitize' => 'absint'],
        '_product_stock'    => ['key' => 'product_stock',    'sanitize' => 'absint'],
        '_product_sku'      => ['key' => 'product_sku',      'sanitize' => 'sanitize_text_field'],
        '_product_notes'    => ['key' => 'product_notes',    'sanitize' => 'sanitize_textarea_field'],
    ];
    
    foreach ($fields as $meta_key => $config) {
        $value = isset($_POST[$config['key']])
            ? call_user_func($config['sanitize'], $_POST[$config['key']])
            : '';
        update_post_meta($post_id, $meta_key, $value);
    }
    
    // チェックボックス(送信されない場合は0)
    $featured = isset($_POST['product_featured']) ? '1' : '0';
    update_post_meta($post_id, '_product_featured', $featured);
}

ステップ4:メタボックスの表示を制御する

// 特定の条件でメタボックスを非表示にする
add_action('do_meta_boxes', 'conditionally_remove_meta_boxes');
function conditionally_remove_meta_boxes(): void {
    // 編集者以下にはメタボックスを非表示
    if (!current_user_can('manage_options')) {
        remove_meta_box('product_details', 'product', 'normal');
    }
}

// デフォルトのメタボックスを削除
add_action('add_meta_boxes', 'remove_default_meta_boxes', 99);
function remove_default_meta_boxes(): void {
    remove_meta_box('trackbacksdiv', 'post', 'normal');    // トラックバック
    remove_meta_box('commentstatusdiv', 'post', 'normal'); // コメント設定
    remove_meta_box('slugdiv', 'post', 'normal');          // スラッグ(上部で編集可能)
}

ステップ5:保存したメタデータをフロントエンドで表示する

// テンプレートファイル(single-product.php など)
if (have_posts()) : while (have_posts()) : the_post();
    $price    = get_post_meta(get_the_ID(), '_product_price', true);
    $stock    = get_post_meta(get_the_ID(), '_product_stock', true);
    $sku      = get_post_meta(get_the_ID(), '_product_sku', true);
    $featured = get_post_meta(get_the_ID(), '_product_featured', true);
?>
    <div class="product-info">
        <h1><?php the_title(); ?></h1>
        <?php if ($featured): ?>
            <span class="badge-featured">おすすめ</span>
        <?php endif; ?>
        <dl>
            <dt>価格</dt>
            <dd>¥<?php echo number_format((int)$price); ?></dd>
            <dt>在庫</dt>
            <dd><?php echo (int)$stock; ?>点</dd>
            <dt>SKU</dt>
            <dd><?php echo esc_html($sku); ?></dd>
        </dl>
        <?php the_content(); ?>
    </div>
<?php endwhile; endif;

注意事項

  • nonce検証は必須です。省略するとCSRFリスクが生じます
  • フロントエンドから直接$_POSTを受け取る場合も必ずサニタイズしてください
  • _product_priceのようにアンダースコアで始まるメタキーは、カスタムフィールドのUIに非表示になります
  • 大量のメタデータはパフォーマンスに影響します。必要最小限のフィールドに留めてください

まとめ

カスタムメタボックスは、①add_meta_boxesフックでの登録、②コールバック関数でのHTML描画(nonce含む)、③save_postフックでのnonce検証・サニタイズ・保存、④remove_meta_box()での不要なボックス削除、⑤テンプレートでの表示の流れで実装します。

お気軽にご相談ください

お見積りへ お問い合わせへ