2026年8月26日
2026年8月26日
WordPressのブロックエディタ(React)でカスタムブロックを作る方法
WordPressのブロックエディタ(Gutenberg)は React で作られており、カスタムブロックも React のコンポーネントとして書きます。この記事では、公式ツール @wordpress/create-block で土台を作り、block.json・編集画面のコンポーネント(edit)・保存処理(save)を自分で書き換えて「お知らせボックス」ブロックを完成させるまでを解説します。読み終えると、属性の設計、サイドバーの設定パネル、静的ブロックと動的ブロックの使い分けまで一通り判断できるようになります。
先に押さえておく仕組み
カスタムブロックは、次の3つで成り立っています。
- block.json:ブロックの名前・属性・対応機能(supports)・読み込むファイルを書くメタデータ。PHP 側と JavaScript 側の両方がこれを読みます。
- edit:エディタの中で表示・編集されるときの React コンポーネント。
- save:保存時に投稿本文へ書き出す HTML を返す関数。ここで返したマークアップが
post_contentにそのまま入ります。
React といっても、react を自分で npm からインストールして使うわけではありません。WordPress には React をラップした @wordpress/element が同梱されていて、ビルドツールの @wordpress/scripts が import 文を WordPress 側のスクリプト(wp-element、wp-block-editor など)への依存に置き換えてくれます。自前で React を同梱すると二重読み込みになり、フックが動かないといった不具合の原因になるので避けてください。
開発環境を用意する
必要なのは Node.js と npm、そしてローカルの WordPress 環境です。ローカル環境は @wordpress/env(Docker が必要)や Local など、普段使っているもので構いません。本番サイトで直接試すのはやめ、ステージングかローカルで作業します。
プラグインフォルダ(wp-content/plugins)で次を実行すると、ブロック用プラグインの雛形ができます。
cd wp-content/plugins
npx @wordpress/create-block@latest notice-box
cd notice-box
npm start
npm start は監視モードでビルドを続け、ファイルを保存するたびに build フォルダが更新されます。本番に持っていくときは npm run build で圧縮版を作ります。管理画面でプラグイン「Notice Box」を有効化すると、ブロック挿入画面に雛形ブロックが出てきます。
生成されるフォルダ構成やメインの PHP ファイルの書き方は、create-block のバージョンによって変わります。最近の雛形は、1つのプラグインに複数のブロックを置ける構成(src/notice-box/ のようにブロックごとのフォルダを作る形)になっていることがあります(2026年10月現在)。この記事のパスは一例なので、手元の雛形に合わせて読み替えてください。
block.json で属性を設計する
最初に決めるべきは「ブロックが何を覚えておくか」、つまり属性(attributes)です。今回はメッセージ本文、アイコンの表示有無、注意レベル(info / warning)の3つを持たせます。
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "my-plugin/notice-box",
"version": "0.1.0",
"title": "お知らせボックス",
"category": "text",
"icon": "megaphone",
"description": "注意書きや補足を枠で囲んで表示します。",
"attributes": {
"message": {
"type": "string",
"source": "html",
"selector": "p"
},
"showIcon": {
"type": "boolean",
"default": true
},
"tone": {
"type": "string",
"enum": [ "info", "warning" ],
"default": "info"
}
},
"supports": {
"html": false,
"color": {
"background": true,
"text": true
},
"spacing": {
"padding": true
}
},
"textdomain": "notice-box",
"editorScript": "file:./index.js",
"editorStyle": "file:./index.css",
"style": "file:./style-index.css"
}
ポイントは message の source: "html" です。これは「保存された HTML の中の p 要素から値を読み戻す」という指定で、本文がコメント区切りの JSON ではなく HTML 本体に入ります。一方 showIcon と tone には source がないので、ブロックのコメント(<!-- wp:my-plugin/notice-box {"tone":"warning"} -->)に保存されます。
supports に color や spacing を書くと、色や余白の設定 UI がコア側で用意され、テーマの theme.json のパレットとも連動します。自前で色選択の UI を作るより、使える supports はまず supports で済ませるほうが、テーマとの整合が取りやすくなります。
edit コンポーネントを書く
エディタ側の見た目と操作は src/notice-box/edit.js(構成によっては src/edit.js)に書きます。
import { __ } from '@wordpress/i18n';
import {
useBlockProps,
RichText,
InspectorControls,
} from '@wordpress/block-editor';
import { PanelBody, ToggleControl, SelectControl } from '@wordpress/components';
export default function Edit( { attributes, setAttributes } ) {
const { message, showIcon, tone } = attributes;
const blockProps = useBlockProps( {
className: `is-tone-${ tone }`,
} );
return (
<>
<InspectorControls>
<PanelBody title={ __( '表示設定', 'notice-box' ) }>
<ToggleControl
label={ __( 'アイコンを表示する', 'notice-box' ) }
checked={ showIcon }
onChange={ ( value ) => setAttributes( { showIcon: value } ) }
/>
<SelectControl
label={ __( '注意レベル', 'notice-box' ) }
value={ tone }
options={ [
{ label: __( '情報', 'notice-box' ), value: 'info' },
{ label: __( '警告', 'notice-box' ), value: 'warning' },
] }
onChange={ ( value ) => setAttributes( { tone: value } ) }
/>
</PanelBody>
</InspectorControls>
<div { ...blockProps }>
{ showIcon && (
<span className="notice-box__icon" aria-hidden="true">
!
</span>
) }
<RichText
tagName="p"
value={ message }
onChange={ ( value ) => setAttributes( { message: value } ) }
placeholder={ __( 'お知らせを入力…', 'notice-box' ) }
/>
</div>
</>
);
}
ここでつまずきやすい点が3つあります。
1つ目は useBlockProps() を外側の要素に展開し忘れることです。これがないと、ブロックの選択枠やツールバー、supports で付けた色・余白のクラスが付きません。「ブロックがクリックできない」「色を変えても反映されない」ときは、まずここを確認します。
2つ目は、属性を直接書き換えようとすることです。attributes.tone = 'warning' のような代入は反映されません。値の変更は setAttributes() 経由で行います。
3つ目は、@wordpress/components のバージョンが上がると、コンポーネントに新しい props を付けるよう促す非推奨の警告がブラウザのコンソールに出ることです。動作には影響しないことが多いものの、警告文に推奨される props が書かれているので、そのとおりに追加しておくと後のバージョンで見た目が崩れにくくなります。
save 関数を書く
save.js は、保存時に本文へ書き出す HTML を返します。
import { useBlockProps, RichText } from '@wordpress/block-editor';
export default function save( { attributes } ) {
const { message, showIcon, tone } = attributes;
const blockProps = useBlockProps.save( {
className: `is-tone-${ tone }`,
} );
return (
<div { ...blockProps }>
{ showIcon && (
<span className="notice-box__icon" aria-hidden="true">
!
</span>
) }
<RichText.Content tagName="p" value={ message } />
</div>
);
}
index.js では block.json を読み込み、edit と save を登録します。
import { registerBlockType } from '@wordpress/blocks';
import './style.scss';
import './editor.scss';
import Edit from './edit';
import save from './save';
import metadata from './block.json';
registerBlockType( metadata.name, {
edit: Edit,
save,
} );
save で特に気を付けたいのが「ブロックの検証エラー」です。エディタは投稿を開くたびに、保存済みの HTML と、現在の save 関数が返す HTML を比べます。公開後に save のマークアップを変えると両者が一致しなくなり、「このブロックには想定されていないコンテンツが含まれています」といった趣旨のエラーが出ます(表記はバージョンで異なります)。公開済みのブロックの出力を変えるときは、registerBlockType() に渡す設定へ deprecated を追加して旧バージョンの save を残すか、次に説明する動的ブロックにしておくのが安全です。
動的ブロックにするという選択
最新記事の一覧や、ログイン状態で表示を変える部分のように、表示内容が保存時点で決まらないものは「動的ブロック」にします。block.json に "render": "file:./render.php" を追加し、save は null を返すようにすると、表示のたびに PHP がマークアップを組み立てます。
<?php
/**
* render.php
* $attributes(属性)、$content(内側のブロック)、$block(WP_Block)が使えます。
*/
$tone = ( isset( $attributes['tone'] ) && 'warning' === $attributes['tone'] ) ? 'warning' : 'info';
$message = isset( $attributes['message'] ) ? $attributes['message'] : '';
?>
<div <?php echo get_block_wrapper_attributes( array( 'class' => 'is-tone-' . $tone ) ); ?>>
<p><?php echo wp_kses_post( $message ); ?></p>
</div>
get_block_wrapper_attributes() は JavaScript 側の useBlockProps.save() に相当し、supports の色や余白のクラスを付けてくれます。属性の値は利用者が入力したものなので、出力時には esc_html() や wp_kses_post() でエスケープします。なお動的ブロックでは message の source: "html" は使えない(保存された HTML がないため)ので、source を外してコメント側に保存する形に変えます。
動的ブロックは出力を後から変えても検証エラーが起きない反面、表示のたびに PHP が動くので、重い処理を入れるとページ全体が遅くなります。キャッシュの効き方も含めて、静的で済むものは静的にしておくのが基本です。
プラグインとして登録する
PHP 側では block.json を読み込んでブロックを登録します。雛形のメインファイルにすでに書かれていますが、最小構成は次のとおりです。
<?php
/**
* Plugin Name: Notice Box
* Description: お知らせボックスのブロックを追加します。
* Version: 0.1.0
* Text Domain: notice-box
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
add_action( 'init', function () {
register_block_type( __DIR__ . '/build/notice-box' );
} );
register_block_type() に build 後のフォルダ(block.json があるフォルダ)を渡すと、block.json に書いたスクリプトとスタイルも自動で登録されます。src ではなく build を指定する点を間違えると、ブロックが挿入画面に出てこなくなります。テーマの functions.php に書くこともできますが、テーマを変えるとブロックの中身が表示されなくなるため、ブロックはプラグインとして分けておくのがおすすめです。
動作確認のチェックポイント
完成したら、次の順で確かめます。
- ブロックを挿入し、文字入力・アイコン切り替え・注意レベルの変更がエディタ上ですぐ反映されるか
- 保存してページを再読み込みしたとき、検証エラーが出ずに同じ状態で開けるか
- 公開ページで、エディタと同じクラス(
is-tone-warningなど)が付いているか - ブラウザの開発者ツールのコンソールに、エラーや非推奨の警告が出ていないか
npm run buildで作った本番用ファイルでも同じように動くか
フロント側で開閉やタブ切り替えのような操作を付けたくなったら、React をフロントに読み込むのではなく、コアの Interactivity API を使う方法があります。ページの重さを抑えつつ、ブロック単位で動きを付けられます。
まとめ
- カスタムブロックは block.json・edit・save の3つで構成され、React は WordPress 同梱のもの(
@wordpress/element)を使う - 土台は
npx @wordpress/create-block@latestで作り、生成されたファイル構成に合わせて読み替える - 属性は block.json で設計し、値の変更は
setAttributes()、外側の要素にはuseBlockProps()を展開する - 公開後に save の出力を変えると検証エラーになるため、
deprecatedで旧版を残すか、動的ブロック(render.php)を選ぶ - 動的ブロックの出力はエスケープし、重い処理は避ける。ブロックはテーマではなくプラグインとして分ける