2026年9月17日

2026年9月17日

WordPressのメタボックスが保存されない問題を解決する方法

はじめに

WordPressでadd_meta_box()でカスタムメタボックスを追加したのに保存ボタンを押してもデータが消える・get_post_meta()で取得すると空になる・save_postフックで処理しているのにデータが保存されない・ノンスの検証がいつも失敗する・ブロックエディタ(Gutenberg)に切り替えたらメタボックスが表示されなくなったといった問題は、save_postフックの処理順序とノンス検証の実装で解決できます。

症状・原因

  • save_postフックでノンスの検証をしていないかアクション名が不一致
  • 自動保存(autosave)のタイミングでもsave_postが発火して処理が中断される
  • ユーザーの権限チェックを行っていないため保存処理がスキップされている
  • ブロックエディタではshow_in_restとregister_post_meta()の設定が必要

解決手順

ステップ1:メタボックスの状態を診断する

# 投稿のカスタムフィールドを確認
wp post meta list 123 --format=table

# 特定のメタ値を取得
wp post meta get 123 my_custom_field

# save_post フックに登録されているコールバックを確認
wp eval "
global \$wp_filter;
if (isset(\$wp_filter['save_post'])) {
    foreach (\$wp_filter['save_post']->callbacks as \$priority => \$callbacks) {
        foreach (\$callbacks as \$cb) {
            echo \$priority . ': ';
            echo is_array(\$cb['function'])
                ? get_class(\$cb['function'][0]) . '::' . \$cb['function'][1]
                : \$cb['function'];
            echo PHP_EOL;
        }
    }
}
"

ステップ2:メタボックスを正しく登録・表示する

// add_meta_box を add_meta_boxes フックで登録
add_action('add_meta_boxes', function(): void {
    add_meta_box(
        'my_meta_box',          // メタボックスID
        '追加情報',              // タイトル
        'render_my_meta_box',   // コールバック
        ['post', 'news'],       // 対象投稿タイプ
        'normal',               // 位置(normal/side/advanced)
        'high'                  // 優先度
    );
});

function render_my_meta_box(WP_Post $post): void {
    // ノンスフィールドを必ず出力
    wp_nonce_field('my_meta_box_save', 'my_meta_box_nonce');

    $value = get_post_meta($post->ID, '_my_custom_field', true);
    ?>
    <label for="my_custom_field">カスタム値:</label>
    <input
        type="text"
        id="my_custom_field"
        name="my_custom_field"
        value="<?php echo esc_attr($value); ?>"
        class="widefat"
    >
    <?php
}

ステップ3:save_postで正しく保存する

add_action('save_post', function(int $post_id): void {
    // ① 自動保存をスキップ
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
        return;
    }

    // ② ノンスの検証
    if (
        !isset($_POST['my_meta_box_nonce']) ||
        !wp_verify_nonce($_POST['my_meta_box_nonce'], 'my_meta_box_save')
    ) {
        return;
    }

    // ③ 権限チェック
    if (!current_user_can('edit_post', $post_id)) {
        return;
    }

    // ④ リビジョンをスキップ
    if (wp_is_post_revision($post_id)) {
        return;
    }

    // ⑤ データをサニタイズして保存
    if (isset($_POST['my_custom_field'])) {
        $value = sanitize_text_field($_POST['my_custom_field']);
        update_post_meta($post_id, '_my_custom_field', $value);
    } else {
        delete_post_meta($post_id, '_my_custom_field');
    }
});

ステップ4:ブロックエディタでメタボックスを使う

// ① メタボックスをブロックエディタでも表示(非推奨だが互換性のため)
// register_post_type の show_in_rest が true でも動く

// ② REST API 経由でカスタムフィールドを保存する(推奨)
add_action('init', function(): void {
    register_post_meta('post', '_my_custom_field', [
        'show_in_rest'      => true,
        'single'            => true,
        'type'              => 'string',
        'sanitize_callback' => 'sanitize_text_field',
        'auth_callback'     => fn() => current_user_can('edit_posts'),
    ]);
});

// ③ ブロックエディタ用のサイドバーパネルをJSで追加
add_action('enqueue_block_editor_assets', function(): void {
    wp_enqueue_script(
        'my-meta-panel',
        get_stylesheet_directory_uri() . '/js/meta-panel.js',
        ['wp-plugins', 'wp-edit-post', 'wp-components', 'wp-data'],
        '1.0.0'
    );
});
// js/meta-panel.js: ブロックエディタのサイドバーにカスタムフィールドを追加
const { registerPlugin } = wp.plugins;
const { PluginDocumentSettingPanel } = wp.editPost;
const { TextControl } = wp.components;
const { useSelect, useDispatch } = wp.data;

registerPlugin('my-meta-panel', {
    render() {
        const meta = useSelect(select =>
            select('core/editor').getEditedPostAttribute('meta')
        );
        const { editPost } = useDispatch('core/editor');

        return wp.element.createElement(
            PluginDocumentSettingPanel,
            { name: 'my-meta', title: '追加情報' },
            wp.element.createElement(TextControl, {
                label: 'カスタム値',
                value: meta._my_custom_field || '',
                onChange: val => editPost({ meta: { _my_custom_field: val } }),
            })
        );
    },
});

ステップ5:複数フィールドの一括保存

// 複数フィールドを持つメタボックスの保存
function save_my_meta_fields(int $post_id): void {
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
    if (!wp_verify_nonce($_POST['my_fields_nonce'] ?? '', 'my_fields_save')) return;
    if (!current_user_can('edit_post', $post_id)) return;

    $fields = [
        '_price'       => 'absint',
        '_description' => 'sanitize_textarea_field',
        '_url'         => 'esc_url_raw',
        '_date'        => 'sanitize_text_field',
    ];

    foreach ($fields as $key => $sanitizer) {
        if (isset($_POST[$key])) {
            update_post_meta($post_id, $key, $sanitizer($_POST[$key]));
        }
    }
}
add_action('save_post_news', 'save_my_meta_fields');  // 特定の投稿タイプのみ

注意事項

  • メタキー名の先頭に_(アンダースコア)を付けるとカスタムフィールドUIに表示されなくなります。プラグイン独自のフィールドには_を推奨します
  • ブロックエディタでregister_post_meta()を使う場合、auth_callbackを設定しないと未認証ユーザーが値を変更できる可能性があります

まとめ

メタボックス保存問題の解決は①wp post meta listで保存状態確認・save_postフックの登録状況をwp_filterで確認、②add_meta_boxesフックで登録・wp_nonce_field()でノンスフィールドを必ず出力・esc_attr()でXSS対策、③DOING_AUTOSAVEチェック・wp_verify_nonce()でノンス検証・current_user_can()で権限確認・wp_is_post_revision()でリビジョンスキップ、④ブロックエディタはregister_post_meta()でshow_in_rest=true・enqueue_block_editor_assetsでJSパネル追加、⑤save_post_{post_type}で特定投稿タイプのみに絞り込み・複数フィールドはサニタイザーのマップで一括処理の手順で解決します。

お気軽にご相談ください

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