2026年10月13日

2026年10月13日

WordPressのブロックエディターにサイドバーパネルを追加する方法

はじめに

「ブロックエディターの右サイドバーに独自の設定パネルを追加したい」「投稿メタデータをエディターから管理できるUIを作りたい」「ブロックエディターにカスタムプラグインUIを統合したい」——@wordpress/pluginsと@wordpress/edit-postを使うとGutenbergのUIを拡張できます。

症状・原因

カスタムメタデータをクラシックエディターのメタボックスで管理していたが、ブロックエディターに対応した洗練されたUIが必要になった場合に実装します。PluginDocumentSettingPanelは投稿設定の「ステータス & 公開状態」パネルの下に追加され、PluginSidebarは独立したサイドバーとして追加されます。

解決手順

ステップ1:プロジェクトのセットアップ

# プラグインのビルド環境を構築
mkdir my-editor-plugin && cd my-editor-plugin
npm init -y
npm install --save-dev @wordpress/scripts

# package.json のスクリプトを設定
# "scripts": {
#   "start": "wp-scripts start",
#   "build": "wp-scripts build"
# }

npm run start
// my-editor-plugin.php: プラグインのメインファイル
<?php
/**
 * Plugin Name: My Editor Plugin
 * Description: ブロックエディターにカスタムパネルを追加
 * Version: 1.0.0
 */

add_action( 'enqueue_block_editor_assets', function(): void {
    $asset = require plugin_dir_path( __FILE__ ) . 'build/index.asset.php';

    wp_enqueue_script(
        'my-editor-plugin',
        plugins_url( 'build/index.js', __FILE__ ),
        $asset['dependencies'],
        $asset['version']
    );
} );

// カスタムメタフィールドをREST APIに公開
add_action( 'init', function(): void {
    register_post_meta( 'post', '_article_note', [
        'show_in_rest'  => true,
        'single'        => true,
        'type'          => 'string',
        'auth_callback' => fn() => current_user_can( 'edit_posts' ),
    ] );

    register_post_meta( 'post', '_is_featured', [
        'show_in_rest'  => true,
        'single'        => true,
        'type'          => 'boolean',
        'auth_callback' => fn() => current_user_can( 'edit_posts' ),
    ] );

    register_post_meta( 'post', '_reading_time', [
        'show_in_rest'  => true,
        'single'        => true,
        'type'          => 'integer',
        'auth_callback' => fn() => current_user_can( 'edit_posts' ),
    ] );
} );

ステップ2:PluginDocumentSettingPanelで投稿設定パネルを追加する

// src/index.js
import { registerPlugin } from '@wordpress/plugins';
import { PluginDocumentSettingPanel } from '@wordpress/editor';
import { useSelect, useDispatch } from '@wordpress/data';
import { store as coreStore } from '@wordpress/core-data';
import { store as editorStore } from '@wordpress/editor';
import {
    TextareaControl,
    ToggleControl,
    TextControl,
    PanelRow,
} from '@wordpress/components';
import { __ } from '@wordpress/i18n';

function MyDocumentPanel() {
    const { editPost } = useDispatch( editorStore );

    const { note, isFeatured, readingTime, postType } = useSelect( ( select ) => {
        const { getEditedPostAttribute, getCurrentPostType } = select( editorStore );
        const meta = getEditedPostAttribute( 'meta' ) || {};
        return {
            note:        meta._article_note   || '',
            isFeatured:  meta._is_featured    || false,
            readingTime: meta._reading_time   || 0,
            postType:    getCurrentPostType(),
        };
    }, [] );

    // post 投稿タイプのみ表示
    if ( postType !== 'post' ) {
        return null;
    }

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

    return (
        <PluginDocumentSettingPanel
            name="my-document-panel"
            title={ __( '記事の詳細設定', 'my-editor-plugin' ) }
            icon="admin-settings"
        >
            <PanelRow>
                <ToggleControl
                    label={ __( '注目記事', 'my-editor-plugin' ) }
                    help={ isFeatured
                        ? __( 'トップページに表示されます', 'my-editor-plugin' )
                        : __( '通常表示', 'my-editor-plugin' )
                    }
                    checked={ isFeatured }
                    onChange={ ( val ) => updateMeta( '_is_featured', val ) }
                />
            </PanelRow>
            <PanelRow>
                <TextControl
                    label={ __( '読了時間(分)', 'my-editor-plugin' ) }
                    type="number"
                    value={ readingTime }
                    min={ 1 }
                    onChange={ ( val ) => updateMeta( '_reading_time', parseInt( val, 10 ) ) }
                    style={ { width: '80px' } }
                />
            </PanelRow>
            <PanelRow>
                <TextareaControl
                    label={ __( '編集メモ(公開されません)', 'my-editor-plugin' ) }
                    value={ note }
                    onChange={ ( val ) => updateMeta( '_article_note', val ) }
                    rows={ 4 }
                />
            </PanelRow>
        </PluginDocumentSettingPanel>
    );
}

registerPlugin( 'my-document-panel', {
    render: MyDocumentPanel,
    icon:   'admin-settings',
} );

ステップ3:PluginSidebarで独立したサイドバーを追加する

// src/sidebar.js: 独立したサイドバーパネル
import { registerPlugin } from '@wordpress/plugins';
import { PluginSidebar, PluginSidebarMoreMenuItem } from '@wordpress/editor';
import { useSelect } from '@wordpress/data';
import { store as editorStore } from '@wordpress/editor';
import { PanelBody, PanelRow, Button, Notice } from '@wordpress/components';
import { __ } from '@wordpress/i18n';

function MySidebar() {
    const { wordCount, characterCount, postTitle } = useSelect( ( select ) => {
        const { getEditedPostAttribute, getEditedPostContent } = select( editorStore );
        const content = getEditedPostContent() || '';
        const text    = content.replace( /<[^>]+>/g, '' ); // タグ除去
        return {
            wordCount:      text.split( /\s+/ ).filter( Boolean ).length,
            characterCount: text.length,
            postTitle:      getEditedPostAttribute( 'title' ) || '',
        };
    }, [] );

    const titleLength = postTitle.length;
    const titleColor  = titleLength > 60 ? '#d63638' : titleLength > 40 ? '#f0c30f' : '#00a32a';

    return (
        <>
            <PluginSidebarMoreMenuItem target="my-sidebar" icon="chart-bar">
                { __( 'SEO・統計情報', 'my-editor-plugin' ) }
            </PluginSidebarMoreMenuItem>

            <PluginSidebar
                name="my-sidebar"
                title={ __( 'SEO・統計情報', 'my-editor-plugin' ) }
                icon="chart-bar"
            >
                <PanelBody title={ __( 'タイトル分析', 'my-editor-plugin' ) } initialOpen={ true }>
                    <PanelRow>
                        <span>{ __( 'タイトル文字数:', 'my-editor-plugin' ) }</span>
                        <strong style={ { color: titleColor } }>{ titleLength }文字</strong>
                    </PanelRow>
                    { titleLength > 60 && (
                        <Notice status="warning" isDismissible={ false }>
                            { __( 'タイトルが60文字を超えています。SEOのため60文字以内を推奨します。', 'my-editor-plugin' ) }
                        </Notice>
                    ) }
                </PanelBody>

                <PanelBody title={ __( '本文統計', 'my-editor-plugin' ) } initialOpen={ true }>
                    <PanelRow>
                        <span>{ __( '文字数:', 'my-editor-plugin' ) }</span>
                        <strong>{ characterCount.toLocaleString() }文字</strong>
                    </PanelRow>
                    <PanelRow>
                        <span>{ __( '単語数:', 'my-editor-plugin' ) }</span>
                        <strong>{ wordCount.toLocaleString() }語</strong>
                    </PanelRow>
                    <PanelRow>
                        <span>{ __( '推定読了時間:', 'my-editor-plugin' ) }</span>
                        <strong>{ Math.max( 1, Math.round( characterCount / 400 ) ) }分</strong>
                    </PanelRow>
                </PanelBody>
            </PluginSidebar>
        </>
    );
}

registerPlugin( 'my-sidebar', {
    render: MySidebar,
} );

ステップ4:既存のメタボックスをAPIで非表示にする

// functions.php: 既存のメタボックスをブロックエディターで非表示
add_action( 'add_meta_boxes', function(): void {
    // カスタムフィールドメタボックスを非表示(REST APIで管理するため)
    remove_meta_box( 'postcustom', 'post', 'normal' );
}, 99 );

// ブロックエディターのデフォルトパネルを非表示
add_filter( 'default_hidden_meta_boxes', function( array $hidden ): array {
    $hidden[] = 'tagsdiv-post_tag'; // タグパネルを初期非表示に
    return $hidden;
}, 10 );

ステップ5:ビルドとWordPressへの組み込み

# 本番用ビルド
npm run build

# ディレクトリ構成
my-editor-plugin/
├── build/
│   ├── index.js          # バンドルされたJS
│   └── index.asset.php   # 依存関係(自動生成)
├── src/
│   └── index.js
└── my-editor-plugin.php

注意事項

  • register_post_meta()にshow_in_rest: trueを設定しないと、JavaScriptからuseSelectでメタデータを読み取れません。
  • PluginDocumentSettingPanelは@wordpress/editorから、PluginSidebarも@wordpress/editorからインポートします(旧バージョンでは@wordpress/edit-postでしたが非推奨になりました)。
  • useDispatch(editorStore).editPost()で変更したメタはUIに即反映されますが、実際の保存は投稿の「更新」ボタンを押したときです。

まとめ

Gutenbergサイドバーパネルの実装は「register_post_meta()でshow_in_rest:trueのメタを登録→enqueue_block_editor_assetsでJSを読み込み→PluginDocumentSettingPanelで投稿設定パネルを追加→useSelectでメタ取得・useDispatch(editorStore).editPost()でメタ更新→PluginSidebarで独立サイドバーを追加→npm run buildでビルド」の流れで整備します。関連記事:WordPressにカスタムブロックを追加する方法、WordPressのブロックパターンを作成する方法。

お気軽にご相談ください

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