WordPressのカスタムフィールドをACFなしで実装する方法

2026.03.10 09:00
2026.03.16 13:32
WordPressのカスタムフィールドをACFなしで実装する方法

WordPressでカスタムフィールドといえば、多くの人が真っ先に思い浮かべるのがAdvanced Custom Fields(ACF)だと思います。実際、これまでACFにかなりお世話になってきました。でも最近、「本当にACFが必要なケースってどれくらいあるんだろう?」と考えるようになったんですよね。

今回は、ACFを使わずにWordPressのネイティブ機能だけでカスタムフィールドを実装する方法を、実際のコードとともにまとめてみました。

ACFに頼りすぎる問題

ACFは素晴らしいプラグインなんですけど、何でもかんでもACFで解決しようとすると、いくつかの問題が出てきます。

  • プラグイン依存:ACFを無効化した瞬間、管理画面からフィールドが消える。データ自体はwp_postmetaに残るけど、編集UIが使えなくなる
  • パフォーマンス:ACFはフィールドグループの定義をDBから読み込むため、フィールドが増えるとクエリ数が増加する。特にPro版でリピーターやフレキシブルコンテンツを多用すると顕著
  • アップデートリスク:ACFのメジャーアップデートで仕様が変わると、既存のフィールド設定が壊れることがある
  • 学習コスト:ACFの関数(get_field、the_fieldなど)はWordPressのネイティブ関数のラッパーで、裏側の仕組みを理解しないまま使っている人が多い

テキストフィールドを数個追加したいだけなら、ネイティブの仕組みで十分対応できます。

register_meta()でカスタムフィールドを登録する

WordPress 4.6から使えるregister_meta()は、カスタムフィールドをシステムに正式に登録するための関数です。これを使うことで、REST APIでの公開やサニタイズの設定が一括で行えます。

function register_custom_meta_fields() {
    register_meta( 'post', '_custom_subtitle', array(
        'show_in_rest'  => true,
        'single'        => true,
        'type'          => 'string',
        'default'       => '',
        'sanitize_callback' => 'sanitize_text_field',
        'auth_callback' => function() {
            return current_user_can( 'edit_posts' );
        },
    ) );

    register_meta( 'post', '_custom_price', array(
        'show_in_rest'  => true,
        'single'        => true,
        'type'          => 'integer',
        'default'       => 0,
        'sanitize_callback' => 'absint',
        'auth_callback' => function() {
            return current_user_can( 'edit_posts' );
        },
    ) );
}
add_action( 'init', 'register_custom_meta_fields' );

ポイントは以下の通りです。

  • show_in_rest:trueにすることで、REST APIとGutenbergエディタからアクセス可能になる
  • single:trueで単一の値、falseで配列として扱える
  • type:string、integer、number、booleanなどを指定
  • sanitize_callback:保存時のサニタイズ処理。セキュリティ上、必ず設定しておく
  • auth_callback:誰がこのメタデータを編集できるかを制御する

REST APIでの取得・更新

register_meta()show_in_restをtrueにしておけば、REST APIから簡単にカスタムフィールドの取得・更新ができます。

取得

# 投稿ID=123のカスタムフィールドを取得
curl https://example.com/wp-json/wp/v2/posts/123?_fields=meta

レスポンスのmetaオブジェクトに登録したフィールドが含まれます。

{
  "meta": {
    "_custom_subtitle": "サブタイトルのテスト",
    "_custom_price": 1500
  }
}

更新

# カスタムフィールドを更新
curl -X POST https://example.com/wp-json/wp/v2/posts/123 \
  -H "Content-Type: application/json" \
  -H "Authorization: Basic BASE64_ENCODED_CREDENTIALS" \
  -d '{"meta": {"_custom_subtitle": "新しいサブタイトル", "_custom_price": 2000}}'

JavaScriptからapiFetchを使う場合はこんな感じです。

import apiFetch from '@wordpress/api-fetch';

// 取得
const post = await apiFetch( { path: '/wp/v2/posts/123?_fields=meta' } );
console.log( post.meta._custom_subtitle );

// 更新
await apiFetch( {
    path: '/wp/v2/posts/123',
    method: 'POST',
    data: {
        meta: {
            _custom_subtitle: 'APIから更新',
        },
    },
} );

メタボックスの自作(add_meta_box)

クラシックエディタや、Gutenbergでも「カスタムフィールド」パネルの代わりに使えるメタボックスを自作する方法です。add_meta_boxは昔からあるWordPressの関数ですけど、今でも十分使えます。

// メタボックスを追加
function add_custom_meta_box() {
    add_meta_box(
        'custom_subtitle_box',     // ID
        'サブタイトル設定',           // タイトル
        'render_subtitle_meta_box', // コールバック
        'post',                     // 投稿タイプ
        'side',                     // 位置(normal, side, advanced)
        'high'                      // 優先度
    );
}
add_action( 'add_meta_boxes', 'add_custom_meta_box' );

// メタボックスの表示内容
function render_subtitle_meta_box( $post ) {
    $subtitle = get_post_meta( $post->ID, '_custom_subtitle', true );
    wp_nonce_field( 'custom_subtitle_nonce', 'subtitle_nonce' );
    ?>
    <p>
        <label for="custom_subtitle">サブタイトル:</label>
        <input type="text" id="custom_subtitle" name="custom_subtitle"
               value="<?php echo esc_attr( $subtitle ); ?>"
               style="width: 100%;" />
    </p>
    <?php
}

// 保存処理
function save_custom_subtitle( $post_id ) {
    // nonce検証
    if ( ! isset( $_POST['subtitle_nonce'] ) ||
         ! wp_verify_nonce( $_POST['subtitle_nonce'], 'custom_subtitle_nonce' ) ) {
        return;
    }

    // 自動保存をスキップ
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
        return;
    }

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

    if ( isset( $_POST['custom_subtitle'] ) ) {
        update_post_meta(
            $post_id,
            '_custom_subtitle',
            sanitize_text_field( $_POST['custom_subtitle'] )
        );
    }
}
add_action( 'save_post', 'save_custom_subtitle' );

メタボックスのポイントをまとめておきます。

  • nonce検証:CSRF対策として必須。wp_nonce_fieldとwp_verify_nonceをセットで使う
  • 自動保存のスキップ:DOING_AUTOSAVEチェックを入れないと、自動保存のたびにメタデータが上書きされる
  • 権限チェック:current_user_canで編集権限を確認する
  • サニタイズ:保存時にsanitize_text_fieldなどで必ずサニタイズする

Gutenbergのサイドバーパネルで表示する

Gutenbergエディタでは、InspectorControlsを使ってサイドバーにカスタムフィールドの編集UIを追加できます。これがACFなしでも実現できるのが、実はあまり知られていないんですよね。

まず、ブロックエディタ用のJavaScriptファイルを作成します。

import { registerPlugin } from '@wordpress/plugins';
import { PluginDocumentSettingPanel } from '@wordpress/editor';
import { TextControl, __experimentalNumberControl as NumberControl } from '@wordpress/components';
import { useSelect, useDispatch } from '@wordpress/data';
import { useEntityProp } from '@wordpress/core-data';

const CustomFieldsPanel = () => {
    const postType = useSelect( ( select ) =>
        select( 'core/editor' ).getCurrentPostType()
    );

    const [ meta, setMeta ] = useEntityProp( 'postType', postType, 'meta' );

    const updateMeta = ( key, value ) => {
        setMeta( { ...meta, [ key ]: value } );
    };

    return (
        <PluginDocumentSettingPanel
            name="custom-fields-panel"
            title="カスタムフィールド"
            className="custom-fields-panel"
        >
            <TextControl
                label="サブタイトル"
                value={ meta._custom_subtitle || '' }
                onChange={ ( value ) => updateMeta( '_custom_subtitle', value ) }
            />
            <NumberControl
                label="価格"
                value={ meta._custom_price || 0 }
                onChange={ ( value ) => updateMeta( '_custom_price', parseInt( value ) ) }
            />
        </PluginDocumentSettingPanel>
    );
};

registerPlugin( 'custom-fields-sidebar', {
    render: CustomFieldsPanel,
    icon: 'admin-generic',
} );

このスクリプトをエンキューする処理も追加します。

function enqueue_custom_sidebar_script() {
    wp_enqueue_script(
        'custom-fields-sidebar',
        get_template_directory_uri() . '/build/custom-sidebar.js',
        array( 'wp-plugins', 'wp-editor', 'wp-components', 'wp-data', 'wp-core-data', 'wp-element' ),
        filemtime( get_template_directory() . '/build/custom-sidebar.js' ),
        true
    );
}
add_action( 'enqueue_block_editor_assets', 'enqueue_custom_sidebar_script' );

PluginDocumentSettingPanelを使うと、投稿の設定サイドバー(「投稿」タブ)にパネルが追加されます。useEntityPropフックでメタデータの読み書きができるので、保存ボタンを押すと自動的にREST API経由で保存される。ACFの管理画面UIに近い体験をネイティブで実現できるのはなかなか良いですね。

なお、ビルドには@wordpress/scriptsを使います。

npm install @wordpress/scripts --save-dev
npx wp-scripts build src/custom-sidebar.js --output-path=build

ACFとの使い分け

ここまでネイティブ実装の方法を紹介してきましたが、「じゃあACFは不要なのか?」というと、そうでもないです。使い分けの指針をまとめてみました。

ネイティブ実装が向いているケース

  • テキスト、数値、真偽値などシンプルなフィールドが数個だけ必要な場合
  • プラグインへの依存を最小限にしたいプロジェクト
  • REST APIを中心としたヘッドレスWordPressの構成
  • パフォーマンスを極限まで最適化したい場合
  • WordPressの内部構造を学びたい場合

ACFが向いているケース

  • リピーターフィールドフレキシブルコンテンツなど、複雑なデータ構造が必要な場合
  • 非エンジニアのクライアントが管理画面を操作する場合(GUIでのフィールド設定が便利)
  • フィールド数が20個以上ある大規模な構成
  • 関連投稿や画像ギャラリーなど、リッチなUIが必要なフィールドタイプ
  • 開発スピードを優先したい場合

個人的な目安としては、フィールド数が5個以下ならネイティブ、10個以上ならACFを検討する、という感じでやっています。5〜10個の間はプロジェクトの性質次第で判断ですね。

まとめ

WordPressのカスタムフィールドは、ACFがなくても十分実装できます。register_meta()でフィールドを登録し、REST APIで取得・更新し、メタボックスやGutenbergサイドバーでUIを提供する。この一連の流れを理解しておくと、ACFを使う場合でも「裏で何が起きているか」がわかるようになりますね。

まずは小さなプロジェクトでネイティブ実装を試してみるのがおすすめです。プラグインに依存しすぎない開発スタイルは、長期的に見てメンテナンスコストの削減にもつながる、、はず!

今回は以上です!