2026年5月17日
2026年5月17日
WordPressのカスタムフィールドをプラグインなしで実装する方法
はじめに
「ACFを使わずにカスタムフィールドを自前実装したい」「メタボックスで管理画面に独自の入力フィールドを追加したい」「プラグインへの依存を減らしてシンプルに運用したい」——add_meta_box()を使えばACFなしでも柔軟なカスタムフィールドが実装できます。
症状・原因
ACFや似たプラグインは手軽ですが、追加のDBクエリや管理画面JS読み込みが発生します。投稿タイプごとに少数の固定フィールドだけ必要な場合は、add_meta_box()とupdate_post_meta()で自前実装した方が軽量です。
解決手順
ステップ1:メタボックスを登録する
// functions.php: カスタムフィールド用メタボックスを追加
add_action( 'add_meta_boxes', function() {
add_meta_box(
'my_product_meta', // メタボックスのID
'商品情報', // タイトル
'my_product_meta_callback', // コールバック関数
'product', // 投稿タイプ('post'・'page'・カスタム投稿)
'normal', // 位置(normal/side/advanced)
'high' // 優先度(high/core/default/low)
);
} );
ステップ2:メタボックスのHTMLを出力する
// functions.php: メタボックスのコールバック関数
function my_product_meta_callback( $post ) {
// nonceフィールドでCSRF対策
wp_nonce_field( 'my_product_meta_save', 'my_product_meta_nonce' );
// 既存の値を取得
$price = get_post_meta( $post->ID, '_product_price', true );
$sku = get_post_meta( $post->ID, '_product_sku', true );
$in_stock = get_post_meta( $post->ID, '_product_in_stock', true );
$note = get_post_meta( $post->ID, '_product_note', true );
?>
<table class="form-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" class="regular-text">
</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 ); ?>"
class="regular-text" placeholder="例:PROD-001">
</td>
</tr>
<tr>
<th>在庫状況</th>
<td>
<label>
<input type="checkbox" name="product_in_stock" value="1"
<?php checked( $in_stock, '1' ); ?>>
在庫あり
</label>
</td>
</tr>
<tr>
<th><label for="product_note">備考</label></th>
<td>
<textarea id="product_note" name="product_note"
rows="4" class="large-text"><?php echo esc_textarea( $note ); ?></textarea>
</td>
</tr>
</table>
<?php
}
ステップ3:save_postでサニタイズして保存する
// functions.php: 投稿保存時にカスタムフィールドを保存
add_action( 'save_post_product', function( $post_id ) {
// 自動保存・リビジョンはスキップ
if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
return;
}
// nonceの検証(CSRF対策)
if ( ! isset( $_POST['my_product_meta_nonce'] )
|| ! wp_verify_nonce( $_POST['my_product_meta_nonce'], 'my_product_meta_save' ) ) {
return;
}
// 権限チェック
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
// 価格(整数)
if ( isset( $_POST['product_price'] ) ) {
update_post_meta( $post_id, '_product_price', absint( $_POST['product_price'] ) );
}
// SKU(英数字・ハイフン・アンダースコアのみ許可)
if ( isset( $_POST['product_sku'] ) ) {
$sku = sanitize_text_field( $_POST['product_sku'] );
$sku = preg_replace( '/[^A-Za-z0-9\-_]/', '', $sku );
update_post_meta( $post_id, '_product_sku', $sku );
}
// 在庫(チェックボックス)
$in_stock = isset( $_POST['product_in_stock'] ) ? '1' : '0';
update_post_meta( $post_id, '_product_in_stock', $in_stock );
// 備考(テキストエリア)
if ( isset( $_POST['product_note'] ) ) {
update_post_meta( $post_id, '_product_note', sanitize_textarea_field( $_POST['product_note'] ) );
}
} );
ステップ4:フロントエンドでカスタムフィールドを表示する
<?php
// single-product.php: カスタムフィールドを表示
$post_id = get_the_ID();
$price = get_post_meta( $post_id, '_product_price', true );
$sku = get_post_meta( $post_id, '_product_sku', true );
$in_stock = get_post_meta( $post_id, '_product_in_stock', true );
$note = get_post_meta( $post_id, '_product_note', true );
?>
<div class="product-meta">
<?php if ( $price ) : ?>
<p class="product-price">
<span class="label">価格:</span>
<strong class="price-value">¥<?php echo number_format( (int) $price ); ?></strong>
</p>
<?php endif; ?>
<?php if ( $sku ) : ?>
<p class="product-sku">
<span class="label">商品コード:</span>
<code><?php echo esc_html( $sku ); ?></code>
</p>
<?php endif; ?>
<p class="product-stock <?php echo $in_stock ? 'in-stock' : 'out-of-stock'; ?>">
<?php echo $in_stock ? '✓ 在庫あり' : '✗ 在庫なし'; ?>
</p>
<?php if ( $note ) : ?>
<div class="product-note">
<p><?php echo nl2br( esc_html( $note ) ); ?></p>
</div>
<?php endif; ?>
</div>
ステップ5:WP_Queryでカスタムフィールドを検索条件に使う
// カスタムフィールドの値で投稿を絞り込む
$products_in_stock = new WP_Query( [
'post_type' => 'product',
'meta_query' => [
[
'key' => '_product_in_stock',
'value' => '1',
'compare' => '=',
],
],
'meta_key' => '_product_price',
'orderby' => 'meta_value_num',
'order' => 'ASC',
] );
// 価格範囲で絞り込み
$products_range = new WP_Query( [
'post_type' => 'product',
'meta_query' => [
[
'key' => '_product_price',
'value' => [ 1000, 5000 ],
'type' => 'NUMERIC',
'compare' => 'BETWEEN',
],
],
] );
注意事項
wp_nonce_field()とwp_verify_nonce()によるCSRF対策は必須です。省略するとXSRF攻撃に脆弱になります。- メタキーの先頭にアンダースコア(
_product_price)を付けると、デフォルトの「カスタムフィールド」パネルから非表示になります。管理画面のUIをメタボックスのみに限定したい場合に有効です。 save_post_{post_type}フックを使うと、特定の投稿タイプの保存時のみ処理が実行されます。save_postだと全投稿タイプで発火するため非効率です。
まとめ
カスタムフィールドのプラグインなし実装は「add_meta_box()でメタボックスを登録→コールバック関数でHTMLを出力(nonce付き)→save_post_{type}フックでnonce検証・サニタイズ・update_post_meta()で保存→get_post_meta()でフロントエンドに表示→meta_queryで絞り込み検索」の流れで整備します。関連記事:Advanced Custom Fields(ACF)でカスタムフィールドを管理する方法、WordPressのカスタム投稿タイプを作成する方法。