KEY Gutenbergブロックのキーワ
ード検索を拡張するプラグイン

WordPressのブロックエディタ(Gutenberg)でブロックを挿入するとき、検索ボックスにキーワードを入力して目的のブロックを探します。この検索は便利ですが、標準で登録されているキーワードだけでは目的のブロックがヒットしないことがあります。

たとえば「コードブロック」を探すとき、「pre」と入力しても見つかりません。「blogcard」と入力しても、テーマ独自のブログカードブロックは出てきません。これは、それらのキーワードがブロックに登録されていないからです。

そこで私は、任意のブロックに任意のキーワードを追加する仕組みを作りました。最初はテーマのfunctions.phpに直接コードを書いていましたが、最終的には独立したプラグインとして切り出し、管理画面から設定できるようにしました。

関連記事

1. 最初の実装:テーマのfunctions.phpで動作させる

最初の実装は、テーマのfunctions.phpに以下のようなコードを書く形でした。

最初の実装:functions.php functions.php enqueue_block_ editor_assets JS読み込み add-block-keywords.js wp.domReady() wp.blocks.getBlockTypes() block.keywords += “pre” 課題 テーマ依存・キーワード固定・移植困難
function enqueue_add_block_keywords_script() {
    wp_enqueue_script(
        'add-block-keywords',
        get_stylesheet_directory_uri() . '/js/add-block-keywords.js',
        array('wp-blocks', 'wp-dom-ready', 'wp-edit-post'),
        filemtime(get_stylesheet_directory() . '/js/add-block-keywords.js'),
        true
    );
}
add_action('enqueue_block_editor_assets', 'enqueue_add_block_keywords_script');
Code language: PHP (php)

この関数は、ブロックエディタが読み込まれるときに実行されます。
enqueue_block_editor_assetsというフック(実行タイミング)を使うと、投稿画面や固定ページ編集画面でだけスクリプトを読み込めます1
通常のフロントエンド画面には影響しません。

スクリプト本体のadd-block-keywords.jsでは、JavaScriptを使ってブロックタイプの情報を書き換えます。

wp.domReady(function() {
    const blocks = wp.blocks.getBlockTypes();
    
    blocks.forEach(function(block) {
        if (block.name === 'core/code') {
            block.keywords = [...block.keywords, 'pre'];
        }
        if (block.name === 'cocoon-blocks/blogcard') {
            block.keywords = [...block.keywords, 'bb'];
        }
    });
});
Code language: PHP (php)

wp.domReady()は、WordPressのブロックエディタが準備完了したタイミングで実行される関数です2
この中でwp.blocks.getBlockTypes()を呼ぶと、登録されているすべてのブロックタイプの配列が取得できます3

各ブロックにはname(ブロックID)とkeywords(検索キーワードの配列)が含まれているので、該当するブロックを見つけたらkeywordsに新しいキーワードを追加します。

この実装で基本的な機能は動作しました。「pre」と入力すればコードブロックが、「bb」と入力すればブログカードブロックが検索結果に表示されるようになりました。

しかし、この方式にはいくつかの問題がありました。

1.1. テーマ依存の問題

最大の問題は、この実装がテーマに依存していることです。get_stylesheet_directory_uri()get_stylesheet_directory()は、現在有効なテーマのディレクトリを返す関数です。つまり、テーマを切り替えるとこのコードは動作しなくなります。

また、別のサイトで同じ機能が欲しくなったとき、テーマのファイルをコピーして回るのは現実的ではありません。テーマとは独立した「機能」として切り出す必要がありました。

1.2. コードのメンテナンス性

キーワードの追加ルールがJavaScriptに直接書かれているのも問題でした。新しいルールを追加するたびに、JavaScriptファイルを編集してアップロードし直す必要があります。コードに不慣れな人には難しい作業です。

これらの課題を解決するため、次のステップとしてプラグイン化を決めました。

2. プラグインとして独立させる

WordPressのプラグインは、wp-content/pluginsディレクトリ配下に置かれ、テーマとは独立して動作します。プラグインとして切り出せば、テーマを変更しても機能が失われませんし、他のサイトへの移植も簡単になります。

2.1. パス解決の変更

プラグイン化で最初に対応したのは、ファイルパスの解決方法の変更です。テーマ用の関数からプラグイン用の関数に置き換えます。

// テーマの場合
get_stylesheet_directory_uri() . '/js/add-block-keywords.js'
get_stylesheet_directory() . '/js/add-block-keywords.js'

// プラグインの場合
plugin_dir_url(__FILE__) . 'js/add-block-keywords.js'
plugin_dir_path(__FILE__) . 'js/add-block-keywords.js'
Code language: PHP (php)

plugin_dir_url(__FILE__)は、現在のPHPファイルが置かれているディレクトリのURLを返します。plugin_dir_path(__FILE__)は、同じくファイルシステム上のパスを返します。これらを使えば、プラグインがどこに配置されていても正しくファイルを読み込めます。

2.2. プラグインヘッダーの追加

WordPressにプラグインとして認識させるには、PHPファイルの先頭に特定の形式のコメントを書きます。

<?php
/**
 * Plugin Name: Chiilabo Add Block Keywords
 * Description: Add custom keywords to Gutenberg blocks in the editor
 * Version: 1.0.0
 * Author: Chiilabo
 * License: GPLv2 or later
 */

if (!defined('ABSPATH')) {
    exit;
}
Code language: HTML, XML (xml)

Plugin NameDescriptionなどの行は、管理画面のプラグイン一覧に表示される情報です。if (!defined('ABSPATH'))は、このファイルが直接アクセスされたときに処理を停止するセキュリティ対策です。

これでプラグインとして動作するようになりました。しかし、キーワードの追加ルールはまだJavaScriptに固定されています。次のステップとして、設定画面を作ることにしました。

3. 設定画面の追加:ルールを編集可能にする

設定画面でルール編集 管理画面で設定 pre, core/code bb, cocoon-blocks/blogcard # コメント可能 PHP解析 JSON変換 JS適用

3.1. 設定ページの登録

WordPressの管理画面に設定ページを追加するには、add_options_page()関数を使います。

function chiilabo_abk_add_settings_page() {
    add_options_page(
        'Add Block Keywords',           // ページタイトル
        'Add Block Keywords',           // メニュー項目名
        'manage_options',               // 必要な権限
        'chiilabo-add-block-keywords',  // ページのスラッグ
        'chiilabo_abk_render_settings_page' // 表示用の関数
    );
}
add_action('admin_menu', 'chiilabo_abk_add_settings_page');
Code language: JavaScript (javascript)

これで、管理画面の「設定」メニューに「Add Block Keywords」という項目が追加されます。

3.2. オプションの登録とサニタイズ

設定内容はWordPressのオプションAPIで保存します。
register_setting()で設定項目を登録し、サニタイズ(入力内容の検証と整形)関数を指定します4

register_setting(
    'chiilabo_abk_settings',
    'chiilabo_add_block_keywords_rules',
    array(
        'type' => 'string',
        'sanitize_callback' => 'chiilabo_abk_sanitize_rules_text',
        'default' => '',
    )
);
Code language: PHP (php)

サニタイズ関数では、入力された文字列を1行ずつ処理します。想定する形式は「キーワード, ブロックID」です。

function chiilabo_abk_sanitize_rules_text($value) {
    if (!is_string($value)) {
        return '';
    }
    
    // 改行コードを統一
    $value = str_replace(array("\r\n", "\r"), "\n", $value);
    
    $lines = array_filter(array_map('trim', explode("\n", $value)));
    $out_lines = array();
    
    foreach ($lines as $line) {
        // コメント行をスキップ
        if (strlen($line) > 0 && $line[0] === '#') {
            continue;
        }
        
        $parts = array_map('trim', explode(',', $line, 2));
        if (count($parts) !== 2) {
            continue;
        }
        
        $keyword = sanitize_text_field($parts[0]);
        $block = sanitize_text_field($parts[1]);
        
        if ($keyword === '' || $block === '') {
            continue;
        }
        
        $out_lines[] = $keyword . ', ' . $block;
    }
    
    return implode("\n", $out_lines);
}
Code language: PHP (php)

この関数は、正しい形式の行だけを残し、不正な行は無視します。#で始まる行はコメントとして扱い、保存時に除外します。こうすることで、入力ミスがあっても致命的なエラーにならず、安全に設定を保存できます。

3.3. 設定画面のHTML

設定画面本体は、フォームとテキストエリアで構成します。

function chiilabo_abk_render_settings_page() {
    if (!current_user_can('manage_options')) {
        return;
    }
    
    $value = get_option('chiilabo_add_block_keywords_rules', '');
    ?>
    <div class="wrap">
        <h1>Add Block Keywords</h1>
        
        <form method="post" action="options.php">
            <?php settings_fields('chiilabo_abk_settings'); ?>
            
            <p>1行に1つ、次の形式で入力してください。</p>
            <pre>keyword, block/name</pre>
            <p>例:</p>
            <pre>pre, core/code
bb, cocoon-blocks/blogcard</pre>
            
            <textarea name="chiilabo_add_block_keywords_rules" 
                      rows="12" 
                      class="large-text code"><?php echo esc_textarea($value); ?></textarea>
            
            <?php submit_button(); ?>
        </form>
    </div>
    <?php
}
Code language: JavaScript (javascript)

settings_fields()は、WordPressのセキュリティトークンを出力する関数です。これがないと設定を保存できません。

3.4. PHPで設定を読み込みJavaScriptへ渡す

保存された設定文字列を、JavaScript側で使いやすい形式に変換する必要があります。目標とする形式は、ブロックIDをキーとしてキーワードの配列を値に持つオブジェクトです。

{
    "core/code": ["pre"],
    "cocoon-blocks/blogcard": ["bb"]
}
Code language: JSON / JSON with Comments (json)

この変換をPHP側で行い、wp_add_inline_script()でJavaScriptに渡します。

function chiilabo_abk_get_rules_mapping() {
    $text = get_option('chiilabo_add_block_keywords_rules', '');
    if (!is_string($text) || $text === '') {
        return array();
    }
    
    $text = str_replace(array("\r\n", "\r"), "\n", $text);
    $lines = array_filter(array_map('trim', explode("\n", $text)));
    
    $map = array();
    
    foreach ($lines as $line) {
        if (strlen($line) > 0 && $line[0] === '#') {
            continue;
        }
        
        $parts = array_map('trim', explode(',', $line, 2));
        if (count($parts) !== 2) {
            continue;
        }
        
        $keyword = $parts[0];
        $block = $parts[1];
        
        if ($keyword === '' || $block === '') {
            continue;
        }
        
        if (!isset($map[$block])) {
            $map[$block] = array();
        }
        
        if (!in_array($keyword, $map[$block], true)) {
            $map[$block][] = $keyword;
        }
    }
    
    return $map;
}

function chiilabo_abk_enqueue_script() {
    $handle = 'chiilabo-add-block-keywords';
    $src = plugin_dir_url(__FILE__) . 'js/add-block-keywords.js';
    
    wp_enqueue_script(
        $handle,
        $src,
        array('wp-blocks', 'wp-dom-ready', 'wp-edit-post'),
        filemtime(plugin_dir_path(__FILE__) . 'js/add-block-keywords.js'),
        true
    );
    
    $mapping = chiilabo_abk_get_rules_mapping();
    
    wp_add_inline_script(
        $handle,
        'window.ChiilaboAddBlockKeywords = ' . wp_json_encode(array(
            'mapping' => $mapping,
        )) . ';',
        'before'
    );
}
add_action('enqueue_block_editor_assets', 'chiilabo_abk_enqueue_script');
Code language: PHP (php)

wp_add_inline_script()の第3引数に'before'を指定すると、メインのスクリプトより前にコードが出力されます5
これでJavaScript側は、読み込まれた時点で window.ChiilaboAddBlockKeywords.mappingからデータにアクセスできます。

3.5. JavaScript側の処理を動的に変更

JavaScript側は、固定のif文ではなく、渡されたマッピングを参照するように書き直します。

wp.domReady(function() {
    const config = window.ChiilaboAddBlockKeywords || {};
    const mapping = config.mapping || {};
    
    const blocks = wp.blocks.getBlockTypes();
    
    blocks.forEach(function(block) {
        const extra = mapping[block.name];
        if (!Array.isArray(extra) || extra.length === 0) return;
        
        const keywords = Array.isArray(block.keywords) ? block.keywords : [];
        
        // 結合して重複を排除
        const merged = [...keywords];
        extra.forEach(function(kw) {
            if (typeof kw === 'string' && kw.trim() && !merged.includes(kw)) {
                merged.push(kw);
            }
        });
        
        block.keywords = merged;
    });
});
Code language: JavaScript (javascript)

この実装のポイントは、block.keywordsが未定義の場合に備えて、Array.isArray()でチェックしている点です。一部のブロックではkeywordsプロパティが存在しないことがあり、そのまま配列操作をするとエラーになります。

これで、設定画面からキーワードのルールを編集できるようになりました。コードを直接触る必要はなくなり、管理画面だけで完結します。

4. ブロックID一覧の表示:試行錯誤の始まり

設定画面で「キーワード, ブロックID」を入力してもらう仕組みはできました。
しかし、ユーザーが正しいブロックIDを知らなければ、設定のしようがありません。

ブロックID一覧:試行錯誤 試行1:JavaScript wp.blocks.getBlockTypes() ❌ 初期化不足で空配列 試行2:REST API /wp/v2/block-types ❌ API無効化で使用不可 なぜ失敗したのか 1 設定画面 ≠ エディタ画面 2 ブロック登録の初期化タイミング違い 3 セキュリティ要件(REST無効化)

そこで、「現在登録されているブロックのID一覧」を設定画面に表示することにしました。これが、予想以上に難しい課題となりました。

4.1. 最初の試み:設定画面でwp.blocks.getBlockTypes()を使う

ブロックエディタで動作するJavaScriptでは、wp.blocks.getBlockTypes()でブロック一覧を取得できます。ならば、設定画面でも同じことをすればいいのではないか。そう考えて実装しました。

まず、設定画面用のJavaScript(admin.js)を作成します。

wp.domReady(function() {
    const blockTypes = wp.blocks.getBlockTypes();
    const list = document.getElementById('block-types-list');
    
    blockTypes.forEach(function(block) {
        const item = document.createElement('div');
        item.textContent = block.name;
        list.appendChild(item);
    });
});
Code language: PHP (php)

そして、このスクリプトを設定画面で読み込みます。

function chiilabo_abk_enqueue_admin_script($hook) {
    if ($hook !== 'settings_page_chiilabo-add-block-keywords') {
        return;
    }
    
    wp_enqueue_script(
        'chiilabo-abk-admin',
        plugin_dir_url(__FILE__) . 'js/admin.js',
        array('wp-blocks', 'wp-dom-ready'),
        '1.0',
        true
    );
}
add_action('admin_enqueue_scripts', 'chiilabo_abk_enqueue_admin_script');
Code language: PHP (php)

しかし、この実装は実際の環境でうまく動きませんでした。ブロック一覧が空だったり、極端に少なかったりしたのです。

4.2. 設定画面とエディタ画面の違い

問題の原因は、「設定画面」と「ブロックエディタ画面」の初期化状態の違いにありました。

ブロックエディタが開かれると、WordPressは多くのブロックタイプを動的に登録します。これはエディタ特有の初期化プロセスです。しかし、設定画面はエディタではないため、この初期化が完全には行われません。

wp.blocksというオブジェクト自体は読み込めても、その中に登録されるブロックタイプが揃わない。これが、設定画面でgetBlockTypes()を呼んでも結果が不完全になる理由でした。

この問題は、実装方法の選択ミスというより、設計思想の誤りでした。「設定画面でエディタと同じ状態を再現しようとすること」自体が、無理のあるアプローチだったのです。

4.3. 次の試み:REST APIでブロック情報を取得する

設定画面側でブロックレジストリを初期化できないなら、サーバー側に問い合わせればいい。
そう考えると、REST APIを使う方法があります。

WordPressには、/wp/v2/block-typesというエンドポイントがあります。
これは、登録されているブロックタイプの情報をJSON形式で返してくれます。

wp.domReady(function() {
    wp.apiFetch({
        path: '/wp/v2/block-types?context=edit&per_page=100'
    }).then(function(blockTypes) {
        const list = document.getElementById('block-types-list');
        
        blockTypes.forEach(function(block) {
            const item = document.createElement('div');
            item.textContent = block.name;
            list.appendChild(item);
        });
    });
});
Code language: PHP (php)

このアプローチは理論的には正しく、実際に動作するケースも多いでしょう。

4.4. REST API無効化の制約

ただし、セキュリティやパフォーマンスの理由で、WordPressのREST APIを制限または無効化している環境は少なくありません6

REST APIが無効化されていると、wp.apiFetch()は失敗します。

5. 解決策:PHP側のレジストリを直接参照する

問題を整理すると、以下のようになります。

解決策:PHPレジストリ WP_Block_Type_Registry ::get_instance() ->get_all_registered() 実現できたこと REST API不要 初期化問題解消 admin.js削除でシンプル化 確実な一覧取得
  • 目的:ブロックID一覧を設定画面に表示したい
  • 制約:REST APIは使えない。設定画面ではwp.blocksのレジストリが不完全
  • 前提:ブロックIDが欲しいだけで、厳密に「そのページで使えるブロック」を出す必要はない

この整理により、答えは明確なりました。
サーバー側、つまりPHPで登録済みブロックの一覧を取得し、それをHTMLとして出力すればいいのです。

5.1. WP_Block_Type_Registryの活用

WordPressには、WP_Block_Type_Registryというクラスがあります7
これは、サーバー側で登録されたブロックタイプを管理するレジストリです。

$registered = WP_Block_Type_Registry::get_instance()->get_all_registered();
$names = array_keys($registered);
sort($names, SORT_STRING);

$block_list_text = implode("\n", $names);
Code language: PHP (php)

get_all_registered()は、登録されているすべてのブロックタイプを連想配列で返します。キーがブロックIDなので、array_keys()でID一覧を取り出し、ソートします。

これを設定画面で出力すれば、ユーザーは利用可能なブロックIDを確認できます。

function chiilabo_abk_render_settings_page() {
    // (前略:設定フォーム部分)
    
    ?>
    <h2>登録済みブロックID一覧</h2>
    <p>サーバー側で登録済みのブロックタイプ一覧(REST API不要)を表示します。</p>
    <?php
    $registered = WP_Block_Type_Registry::get_instance()->get_all_registered();
    $names = array_keys($registered);
    sort($names, SORT_STRING);
    
    $block_list_text = implode("\n", $names);
    ?>
    <textarea rows="14" class="large-text code" readonly><?php echo esc_textarea($block_list_text); ?></textarea>
    <p class="description">形式: <code>block/name</code></p>
    <?php
}
Code language: HTML, XML (xml)

このアプローチの利点は、以下の通りです。

  1. REST API不要:完全にPHP側で完結し、REST APIの有効・無効に影響されない
  2. シンプル:非同期通信やエラーハンドリングが不要で、コードが単純
  3. 確実:サーバー側で登録されているブロックが確実に取得できる

5.2. admin.jsの削除

この変更により、admin.jsは完全に役割を失いました。
当初この JavaScript ファイルは、「設定画面でブロック一覧をJSで取得して表示する」ためだけに存在していました。

その一覧取得をPHP側で行うように設計変更したため、admin.jsは不要になったのです。
ファイルを削除し、admin_enqueue_scriptsのフックも削除しました。

結果として、コードはより単純になり、依存関係も減りました。

6. ファイル構成と動作フロー

最終的な実装は、以下の構成になりました。

chiilabo-add-block-keywords/
├── chiilabo-add-block-keywords.php  (メインプラグインファイル)
└── js/
    └── add-block-keywords.js        (エディタ用スクリプト)

admin.jsは削除され、必要最小限のファイルだけが残りました。

  1. ユーザーが設定画面でルールを入力・保存
  2. PHPがルールをサニタイズして保存
  3. エディタ画面が開かれると、PHPがルールをマッピングに変換しJavaScriptへ渡す
  4. JavaScriptが各ブロックにキーワードを追加

設定画面には、PHP側で取得したブロックID一覧が表示されるため、ユーザーは正確なIDを確認できます。

6.1. block.keywordsの未定義対策

開発を通じて学んだ、注意すべきポイントをまとめます。

一部のブロックでは、keywordsプロパティが未定義の場合があります。
そのため、JavaScript側では必ず配列かどうかをチェックしてから操作します。

const keywords = Array.isArray(block.keywords) ? block.keywords : [];
Code language: JavaScript (javascript)

この1行を忘れると、特定のブロックでエラーが発生し、すべてのキーワード追加処理が止まってしまいます。

6.2. サニタイズの重要性

ユーザー入力をそのまま保存してはいけません。
必ずsanitize_text_field()などでサニタイズし、想定外の文字や不正な形式を排除します。

また、不正な行があってもエラーで止めず、正しい行だけを保存するようにすると、ユーザーの入力ミスに寛容なシステムになります。

6.3. 設定画面のセキュリティ

設定画面の関数では、必ずcurrent_user_can('manage_options')で権限チェックを行います。
これを忘れると、権限のないユーザーが設定を変更できてしまいます。

function chiilabo_abk_render_settings_page() {
    if (!current_user_can('manage_options')) {
        return;
    }
    // ... 以降の処理
}
Code language: JavaScript (javascript)

7. おわりに

この開発を通じて、「どの層で何を処理すべきか」を見極めることの重要性を再認識しました。

最初は「JavaScriptでできることはJavaScriptでやる」と考え、設定画面でもJavaScriptでブロック一覧を取得しようとしました。
しかし、実際にはそのアプローチには無理がありました。

設定画面とエディタ画面は違う環境であり、同じAPIを使っても同じ結果が得られるとは限りません。
REST APIという選択肢も、制約によって使えないことがあります。

最終的に、「PHPで取得してHTMLで出力する」という最もシンプルな方法に落ち着きました。

  1. enqueue_block_editor_assetsフックは、ブロックエディタ専用のアセット読み込みフックで、管理画面のエディタUIにのみスクリプトやスタイルを追加します。フロントエンドやブロックコンテンツ自体には影響しません。 – Enqueueing assets in the Editor – Block Editor Handbook
  2. wp.domReadyは@wordpress/dom-readyパッケージが提供する関数で、DOMの準備完了後に実行されます。ブロックエディタのスクリプトでは、ブロックタイプの登録やカスタマイズ処理を安全に実行するために広く使われています。
  3. wp.blocks.getBlockTypes()は@wordpress/blocksパッケージのメソッドで、登録済みのすべてのブロックタイプを配列で返します。各ブロックタイプオブジェクトには、name、title、keywords、supportsなどのプロパティが含まれます。 – Block Types Data – Block Editor Handbook
  4. register_setting()は、WordPress Settings APIの一部で、設定項目を登録し、自動的な保存処理とサニタイズ処理を提供します。sanitize_callback引数でカスタムサニタイズ関数を指定することで、不正な入力を排除し、データの整合性を保証できます。 – register_setting() – Function
  5. wp_add_inline_script()は、登録済みスクリプトに追加のJavaScriptコードをインライン出力する関数です。第3引数に’before’を指定するとスクリプト本体の前に、’after’(デフォルト)を指定すると後に出力されます。設定データをJavaScriptに渡す際によく使われます。 – wp_add_inline_script() – Function
  6. WordPress REST APIは、セキュリティ上の理由(ユーザー列挙攻撃の防止など)やパフォーマンス上の理由から、認証済みユーザーのみに制限したり、特定のエンドポイントを無効化したりする運用が推奨されることがあります。多くのセキュリティプラグインがこの機能を提供しています。 – How to Disable JSON REST API in WordPress
  7. WP_Block_Type_Registryは、WordPress 5.0で導入されたブロックタイプを管理するレジストリクラスです。get_instance()でシングルトンインスタンスを取得し、get_all_registered()で登録済みブロックタイプを連想配列として取得できます。 – WP_Block_Type_Registry – Class