2026年6月26日

2026年6月26日

WordPressのメタボックスを自作する方法【add_meta_boxと保存処理】

メタボックスは、投稿や固定ページの編集画面に表示される入力欄のまとまりです。WordPress の add_meta_box() を使うと、独自のメタボックスを追加し、入力された値を投稿のメタデータ(カスタムフィールド)として保存できます。

この記事では、書評記事に「書籍の価格」「ISBN」「おすすめ表示」「内部メモ」の4項目を追加する例で、登録から保存、テーマでの表示までを順に作ります。コードは子テーマの functions.php か、自作のプラグインに書きます。関数と引数は、2026年11月時点で developer.wordpress.org の関数リファレンスを確認した内容です。

全体の流れ

段階 使うフック・関数 内容
登録 add_meta_boxes フック、add_meta_box() 編集画面にメタボックスを出す
描画 コールバック関数、wp_nonce_field() 入力欄と nonce を出力する
保存 save_post フック、update_post_meta() 検証してからメタデータに保存する
表示 get_post_meta() テーマのテンプレートで値を出す

ステップ1:メタボックスを登録する

<?php
add_action( 'add_meta_boxes', 'myprefix_add_book_meta_box' );

function myprefix_add_book_meta_box() {
    add_meta_box(
        'myprefix_book_details',        // メタボックスのID(HTMLのid属性にもなる)
        '書籍の情報',                    // 見出し
        'myprefix_render_book_meta_box', // 中身を出力する関数
        'post',                         // 表示する投稿タイプ(配列で複数指定も可)
        'normal',                       // 位置: normal / side / advanced
        'high'                          // 優先度: high / core / default / low
    );
}

add_meta_box() の引数は、ID・見出し・コールバック・画面(投稿タイプ)・位置・優先度・コールバックに渡す追加の値、の順です。'post' を array( 'post', 'page' ) にすると、投稿と固定ページの両方に表示されます。カスタム投稿タイプに出すなら、その投稿タイプ名を指定します。

7番目の引数に配列を渡すと、コールバックの2番目の引数 $box の $box['args'] で受け取れます。

ステップ2:入力欄を描画する

<?php
function myprefix_render_book_meta_box( $post, $box ) {
    // 保存時の検証に使う nonce を出力する
    wp_nonce_field( 'myprefix_save_book', 'myprefix_book_nonce' );

    $price    = get_post_meta( $post->ID, '_myprefix_book_price', true );
    $isbn     = get_post_meta( $post->ID, '_myprefix_book_isbn', true );
    $featured = get_post_meta( $post->ID, '_myprefix_book_featured', true );
    $notes    = get_post_meta( $post->ID, '_myprefix_book_notes', true );
    ?>
    <p>
        <label for="myprefix_book_price">価格(円)</label><br>
        <input type="number" id="myprefix_book_price" name="myprefix_book_price"
               value="<?php echo esc_attr( $price ); ?>" min="0" step="1">
    </p>
    <p>
        <label for="myprefix_book_isbn">ISBN</label><br>
        <input type="text" id="myprefix_book_isbn" name="myprefix_book_isbn"
               value="<?php echo esc_attr( $isbn ); ?>" class="regular-text">
    </p>
    <p>
        <label>
            <input type="checkbox" name="myprefix_book_featured" value="1"
                <?php checked( $featured, '1' ); ?>>
            おすすめとして表示する
        </label>
    </p>
    <p>
        <label for="myprefix_book_notes">内部メモ(サイトには表示しません)</label><br>
        <textarea id="myprefix_book_notes" name="myprefix_book_notes" rows="3"
                  class="large-text"><?php echo esc_textarea( $notes ); ?></textarea>
    </p>
    <?php
}

ポイントは次のとおりです。

  • 値を入力欄に出すときは、属性値には esc_attr()、テキストエリアには esc_textarea() でエスケープします。
  • checked() は、2つの値が等しいときに checked="checked" を出力する関数です。
  • regular-text・large-text は管理画面に標準で用意されているクラスで、独自のCSSを書かなくても入力欄の幅がそろいます。

ステップ3:保存する

save_post フックで、送信された値を検証してから保存します。

<?php
add_action( 'save_post_post', 'myprefix_save_book_meta' );

function myprefix_save_book_meta( $post_id ) {
    // nonce が無い・正しくない場合は何もしない
    if ( ! isset( $_POST['myprefix_book_nonce'] )
        || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['myprefix_book_nonce'] ) ), 'myprefix_save_book' ) ) {
        return;
    }

    // 自動保存とリビジョンでは保存しない
    if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || wp_is_post_revision( $post_id ) ) {
        return;
    }

    // この投稿を編集する権限があるか
    if ( ! current_user_can( 'edit_post', $post_id ) ) {
        return;
    }

    // 価格: 0以上の整数。空欄なら削除する
    if ( isset( $_POST['myprefix_book_price'] ) && '' !== $_POST['myprefix_book_price'] ) {
        update_post_meta( $post_id, '_myprefix_book_price', absint( $_POST['myprefix_book_price'] ) );
    } else {
        delete_post_meta( $post_id, '_myprefix_book_price' );
    }

    // ISBN: 1行のテキスト
    if ( isset( $_POST['myprefix_book_isbn'] ) ) {
        update_post_meta( $post_id, '_myprefix_book_isbn', sanitize_text_field( wp_unslash( $_POST['myprefix_book_isbn'] ) ) );
    }

    // チェックボックス: チェックが外れていると送信されないので、その場合は '0' を保存する
    update_post_meta( $post_id, '_myprefix_book_featured', isset( $_POST['myprefix_book_featured'] ) ? '1' : '0' );

    // 内部メモ: 改行を残すテキスト
    if ( isset( $_POST['myprefix_book_notes'] ) ) {
        update_post_meta( $post_id, '_myprefix_book_notes', sanitize_textarea_field( wp_unslash( $_POST['myprefix_book_notes'] ) ) );
    }
}
  • save_post_post は save_post_{投稿タイプ} の形のフックで、投稿タイプ post の保存時だけ動きます。全投稿タイプで動く save_post を使う場合は、関数の中で投稿タイプを確かめます。
  • $_POST の値は WordPress によってスラッシュが付けられているため、wp_unslash() で外してからサニタイズします。
  • nonce の確認は、他のサイトから送られた偽の保存要求(CSRF)を防ぐためのものです。権限の確認とあわせて省略しないでください。

ステップ4:不要なメタボックスを消す・権限で出し分ける

標準のメタボックスは remove_meta_box() で消せます。独自のメタボックスより後に処理されるよう、優先度を大きくして登録します。

<?php
add_action( 'add_meta_boxes', 'myprefix_remove_meta_boxes', 99 );

function myprefix_remove_meta_boxes() {
    // カスタムフィールド欄を消す
    remove_meta_box( 'postcustom', 'post', 'normal' );

    // 管理者以外には内部メモを含むメタボックスを見せない
    if ( ! current_user_can( 'manage_options' ) ) {
        remove_meta_box( 'myprefix_book_details', 'post', 'normal' );
    }
}

remove_meta_box() の引数は、ID・投稿タイプ・位置です。位置は登録したときと同じ値にします。クラシックエディターでは、trackbacksdiv(トラックバック送信)、commentstatusdiv(ディスカッション)、slugdiv(スラッグ)なども同じ方法で消せます。ブロックエディターの「ディスカッション」などの設定パネルはメタボックスとは別の仕組みで表示されているため、この方法では消えないことがあります。

ステップ5:テーマで値を表示する

保存した値は、テーマの single.php などで get_post_meta() を使って取り出します。ループの中に書く例です。

<?php
$price    = get_post_meta( get_the_ID(), '_myprefix_book_price', true );
$isbn     = get_post_meta( get_the_ID(), '_myprefix_book_isbn', true );
$featured = get_post_meta( get_the_ID(), '_myprefix_book_featured', true );

if ( '' !== $price || '' !== $isbn ) : ?>
    <div class="book-info">
        <?php if ( '1' === $featured ) : ?>
            <span class="book-info__badge">おすすめ</span>
        <?php endif; ?>
        <dl>
            <?php if ( '' !== $price ) : ?>
                <dt>価格</dt>
                <dd><?php echo esc_html( number_format( (int) $price ) ); ?>円</dd>
            <?php endif; ?>
            <?php if ( '' !== $isbn ) : ?>
                <dt>ISBN</dt>
                <dd><?php echo esc_html( $isbn ); ?></dd>
            <?php endif; ?>
        </dl>
    </div>
<?php endif;

get_post_meta() の3番目の引数を true にすると、値を1つだけ文字列で返します。値が保存されていない場合は空文字が返るので、空かどうかを確かめてから表示します。内部メモはサイトに出さない情報なので、テンプレートでは取り出しません。

メタキーとブロックエディターについて

  • アンダースコアで始まるメタキー(例: _myprefix_book_price)は「保護されたメタデータ」として扱われ、標準のカスタムフィールド欄には表示されません。メタボックスからだけ編集させたい値に向いています。
  • キー名には接頭辞を付けます。_price のような短い名前は、WooCommerce などのプラグインが使う名前と重なるおそれがあります。
  • ブロックエディターでも動きます。add_meta_box() で追加したメタボックスは、ブロックエディターでは本文の下(または右のサイドバー)に表示され、投稿の保存と一緒に保存されます。
  • 値を REST API やブロックから読み書きしたい場合は、register_post_meta() でメタキーを登録し、show_in_rest を有効にする方法があります。

ACFとの使い分け

入力欄が多い、繰り返しの項目や画像の選択欄が必要、コードを書かずに管理したい、という場合は、Advanced Custom Fields(ACF)などのプラグインで項目を作るほうが手早く済みます。項目が数個で、プラグインを増やしたくない場合や、テーマ・自作プラグインと一緒に配布したい場合は、この記事のように add_meta_box() で作るのが向いています。

カスタムフィールドの基本はメタボックスとカスタムフィールドの記事、値が保存されないときの確認点はカスタムフィールドが保存されないときの対処も参考にしてください。

まとめ

  • add_meta_boxes フックで add_meta_box() を呼び、コールバックで入力欄と nonce を出力する。
  • 保存は save_post(または save_post_{投稿タイプ})で、nonce・自動保存・権限を確かめ、wp_unslash() とサニタイズをしてから update_post_meta() で保存する。
  • 不要なメタボックスは優先度を大きくした add_meta_boxes の中で remove_meta_box() を呼んで消す。
  • 表示側は get_post_meta() で取り出し、エスケープして出力する。

お気軽にご相談ください

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