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()での不要なボックス削除、⑤テンプレートでの表示の流れで実装します。