技術ブログ
【WordPress】ブロックエディタにオリジナルのブロックを追加する ── Q&Aブロックを作って動かしてみた
ブロックエディタでは、段落・見出し・画像といった標準のブロックのほかに、自分で作ったブロックを追加できます。たとえば「Q&A」というブロックを1つ選ぶと、質問文・回答文・解説文の3つの入力欄が並び、決まった形で入力してもらう、ということができます。
本記事では、この Q&A ブロックを3通りの方法で実際に作り、WordPress 7.1.3 の検証環境で「挿入 → 入力 → 公開 → 表示」まで動かしました。あわせて、クラシックテーマに Classic Editor が入っている環境で使うときに、どこでつまずくかも実機で確かめています。
① ブロックはプラグインとして作る。いちばん簡単なのはビルドなしの作り方(ファイル4つ・Node.js 不要 → 3章)
② Classic Editor の設定を変えて、ブロックエディタを開けるようにする(→ 7章)
③ カスタム投稿タイプは「REST API で表示」を「はい」にする(→ 7章)
1. 結論
| ご質問 | 答え |
|---|---|
| オリジナルのブロックを追加できるか | できます。WordPress 標準の仕組み(block.json と register_block_type())でプラグインとして追加します。 |
| クラシックテーマのサイトでも使えるか | 使えます。ブロックはテーマではなくプラグインとして入れるので、テーマの種類は問いません。ただし Classic Editor プラグインが有効だとブロックエディタ自体が開かないので、設定を変える必要があります(→ 7章)。 |
| Q&A を「質問文・回答文・解説文」に分けて入力させられるか | できます。1つのブロックの中に入力欄を3つ持たせます。下の画面は、今回実際に作ったブロックです。 |


2. オリジナルブロックの作り方は4通り
| 作り方 | 作るもの | 向いている場面 | 注意 |
|---|---|---|---|
| A. ビルドなし ★いちばん簡単 | block.json+素の JavaScript+表示用の PHP |
本文の中で直接入力させたい/Node.js を用意したくない | JavaScript は JSX を使わない書き方になる |
| B. ビルドあり(公式の推奨) | block.json+JavaScript(JSX)+表示用の PHP |
入力欄を作り込みたい/JSX で書きたい | JavaScript をビルドする環境(Node.js)が必要 |
| C. ACF ブロック | block.json+PHP だけ。入力欄は ACF のフィールドで作る |
PHP とカスタムフィールドに慣れている/ビルド環境を持ちたくない | 無料の ACF には無い機能。ACF PRO(有料)か、SCF(無料)が必要 |
| D. ブロックパターン | 既存のブロック(見出し+段落+段落)の組み合わせを登録 | 「毎回同じ並びで書いてほしい」だけで十分なとき | 入力欄に「これは解説文」という意味を持たせられない。並びも崩せてしまう |
「質問文・回答文・解説文に分けて入力させたい」というご要望には、A・B・C が合います。以下、3つとも実際に作った結果です。
3. 作り方A:ビルドなしで作る(いちばん簡単)
Node.js もビルドも使わずに、ファイル4つで作れます。JavaScript は WordPress が最初から読み込んでいる部品(wp.blocks など)をそのまま呼ぶ書き方にするので、変換(ビルド)が要りません。WordPress の公式ドキュメントでも、ビルドが必要なのは JSX などを使う場合で、素の JavaScript なら不要とされています。

3-1. ファイルの構成
├─ bit-qa-simple.php ← プラグイン本体(ブロックを登録するだけ)
└─ qa/
├─ block.json ← ブロックの定義(名前・入力欄・使うファイル)
├─ index.js ← エディタの入力欄(素の JavaScript)
├─ index.asset.php ← index.js が使う部品の一覧(手で書く)
├─ render.php ← 公開ページの表示
└─ style.css
3-2. block.json
“apiVersion”: 3,
“name”: “bit/qa-simple”,
“title”: “Q&A”,
“category”: “text”,
“icon”: “editor-help”,
“attributes”: {
“question”: { “type”: “string”, “default”: “” },
“answer”: { “type”: “string”, “default”: “” },
“explanation”: { “type”: “string”, “default”: “” }
},
“supports”: { “html”: false },
“editorScript”: “file:./index.js”,
“style”: “file:./style.css”,
“render”: “file:./render.php”
}
3-3. index.js(素の JavaScript・ビルド不要)
var el = wp.element.createElement;
var useBlockProps = wp.blockEditor.useBlockProps;
var RichText = wp.blockEditor.RichText;
// 入力欄1つ分(ラベル+入力)
function field( label, placeholder, value, onChange ) {
return el( ‘div’, { className: ‘qa__row’ },
el( ‘span’, { className: ‘qa__label’ }, label ),
el( RichText, { tagName: ‘p’, placeholder: placeholder, value: value, onChange: onChange } )
);
}
wp.blocks.registerBlockType( ‘bit/qa-simple’, {
edit: function ( props ) {
var a = props.attributes, set = props.setAttributes;
return el( ‘div’, useBlockProps( { className: ‘qa’ } ),
field( ‘質問文’, ‘質問文を入力’, a.question, function ( v ) { set( { question: v } ); } ),
field( ‘回答文’, ‘回答文を入力’, a.answer, function ( v ) { set( { answer: v } ); } ),
field( ‘解説文’, ‘解説文を入力(任意)’, a.explanation, function ( v ) { set( { explanation: v } ); } )
);
},
save: function () { return null; } // 表示は render.php で作る
} );
} )( window.wp );
3-4. index.asset.php(使う部品を書いておく)
return array(
‘dependencies’ => array(‘wp-blocks’, ‘wp-element’, ‘wp-block-editor’),
‘version’ => ‘0.1.0’,
);
ビルドすると自動で作られるファイルを、手で書いたものです。WordPress 7.1.3 のコードでは任意の扱いで、消しても動きました。ただ、書いておかないと
index.js が使う部品(wp-blocks など)を先に読み込む保証がなくなります。
render.php は、作り方B(4章)と同じです。プラグイン本体は register_block_type( __DIR__ . '/qa' ); を init で呼ぶだけです。
3-5. 動かして確かめたこと
ブロック一覧での検索・挿入、3つの欄への入力、「公開」ボタンでの公開、開き直し(壊れない)、公開ページの表示と CSS まで、作り方B と同じ結果になりました。ブラウザに非推奨の警告も出ません。
4. 作り方B:JSX で書いてビルドする(公式の推奨)
4-1. ファイルの構成
├─ bit-qa-block.php ← プラグイン本体(ブロックを登録するだけ)
├─ package.json ← ビルドの設定
├─ src/qa/ ← 書くのはここ
│ ├─ block.json ← ブロックの定義(名前・入力欄・使うファイル)
│ ├─ index.js ← 登録
│ ├─ edit.js ← エディタでの見た目と入力欄
│ ├─ render.php ← 公開ページでの表示(PHP)
│ ├─ style.scss / editor.scss
└─ build/qa/ ← ビルドで自動生成(サーバーに置くのはこちら)
4-2. block.json(入力欄を3つ定義する)
ポイントは attributes です。ここに書いた3つが、そのまま3つの入力欄の保存先になります。
“apiVersion”: 3,
“name”: “bit/qa”,
“title”: “Q&A”,
“category”: “text”,
“icon”: “editor-help”,
“attributes”: {
“question”: { “type”: “string”, “default”: “” },
“answer”: { “type”: “string”, “default”: “” },
“explanation”: { “type”: “string”, “default”: “” }
},
“supports”: { “html”: false },
“editorScript”: “file:./index.js”,
“editorStyle”: “file:./index.css”,
“style”: “file:./style-index.css”,
“render”: “file:./render.php”
}
4-3. edit.js(エディタに入力欄を3つ並べる)
export default function Edit( { attributes, setAttributes } ) {
const { question, answer, explanation } = attributes;
return (
<div { …useBlockProps( { className: ‘qa’ } ) }>
<div className=“qa__row qa__row–q”>
<span className=“qa__label”>質問文</span>
<RichText tagName=“p” placeholder=“質問文を入力”
value={ question } onChange={ ( v ) => setAttributes( { question: v } ) } />
</div>
{/* 回答文・解説文も同じ形で2つ続く(answer / explanation) */}
</div>
);
}
4-4. index.js(登録。保存するのは入力値だけ)
import ‘./style.scss’;
import ‘./editor.scss’;
import Edit from ‘./edit’;
import metadata from ‘./block.json’;
registerBlockType( metadata.name, {
edit: Edit,
save: () => null, // HTML は保存しない。表示は render.php で作る
} );
4-5. render.php(公開ページの表示)
$question = $attributes[‘question’] ?? ”;
$answer = $attributes[‘answer’] ?? ”;
$explanation = $attributes[‘explanation’] ?? ”;
if ($question === ” && $answer === ”) { return; }
?>
<dl <?php echo get_block_wrapper_attributes([‘class’ => ‘qa’]); ?>>
<dt class=“qa__q”><?php echo wp_kses_post($question); ?></dt>
<dd class=“qa__a”><?php echo wp_kses_post($answer); ?></dd>
<?php if ($explanation !== ”) : ?>
<dd class=“qa__note”><?php echo wp_kses_post($explanation); ?></dd>
<?php endif; ?>
</dl>
4-6. プラグイン本体とビルド
/* Plugin Name: bit Q&A Block */
add_action(‘init’, function () {
register_block_type(__DIR__ . ‘/build/qa’);
});
npm install –save-dev @wordpress/scripts
npm run build
webpack 5.112.0 compiled successfully ← build/qa/ に index.js・CSS・block.json・render.php が出る
ビルドに使う
@wordpress/scripts の最新版(37.0.0)は、Node.js 22.22.2 以上(または 24.15.0 以上)が必要です。これは公式の要件で、試しに Node 20(20.20.2)で入れてみると、インストールが途中のエラーで止まりました。今回は Node 24 でビルドしています。サーバーに置くのは
build/ とプラグイン本体の PHP だけで、サーバー側に Node.js は不要です。
4-7. 動かして確かめたこと
| 確かめたこと | 結果 |
|---|---|
| ブロック一覧に出るか | 「テキスト」の分類に「Q&A」が出る |
| 3つの欄に入力して公開できるか | できる。本文には <!-- wp:bit/qa {"question":"…","answer":"…","explanation":"…"} /--> の形で、入力値だけが保存される |
| 保存した記事を開き直すと壊れないか | 壊れない(「このブロックには想定されていないコンテンツが含まれています」は出ない) |
| 解説文を空にしたら | 解説の行ごと出ない(render.php の条件どおり) |
危険な入力(<script>・onerror) |
wp_kses_post() で取り除かれて表示される |
| CSS の読み込み | Q&A ブロックがあるページだけに出る。無いページには出ない |
5. 作り方C:ACF ブロック(PHP だけ)で作る
JavaScript を書かずに、PHP とカスタムフィールドだけでブロックを作る方法です。入力欄はエディタ右側の「ブロック」パネルに出て、本文側には render.php の表示がそのままプレビューされます。

ACF ブロックは ACF 公式で 「ACF PRO の機能」とされています。使えるのは次のどちらかです。
・ACF PRO(有料)
・SCF(Secure Custom Fields):WordPress.org で無料配布されている ACF 系のプラグイン。今回確かめた 6.9.6(2026-10-15 更新)にはブロック機能が入っていて、実際に動きました。ACF とは同時に有効化できないので、切り替えて使います(SCF を有効にしたまま ACF を有効化しようとすると、有効化できませんでした)。
5-1. block.json(”acf” を書くと ACF ブロックになる)
“apiVersion”: 3,
“name”: “bit/qa-acf”,
“title”: “Q&A(ACF版)”,
“category”: “text”,
“icon”: “editor-help”,
“acf”: { “mode”: “preview”, “renderTemplate”: “render.php” }
}
5-2. 入力欄(フィールド)を作ってブロックに割り当てる
管理画面のフィールドグループで作っても同じです。ここでは PHP で書きました。場所(location)を「ブロック = bit/qa-acf」にするのがポイントです。
register_block_type(__DIR__ . ‘/blocks/qa’);
});
add_action(‘acf/include_fields’, function () {
acf_add_local_field_group([
‘key’ => ‘group_bit_qa’,
‘title’ => ‘Q&A’,
‘fields’ => [
[‘key’ => ‘field_bit_qa_question’, ‘label’ => ‘質問文’, ‘name’ => ‘question’, ‘type’ => ‘textarea’, ‘required’ => 1],
[‘key’ => ‘field_bit_qa_answer’, ‘label’ => ‘回答文’, ‘name’ => ‘answer’, ‘type’ => ‘textarea’, ‘required’ => 1],
[‘key’ => ‘field_bit_qa_explanation’, ‘label’ => ‘解説文’, ‘name’ => ‘explanation’, ‘type’ => ‘textarea’],
],
‘location’ => [[[‘param’ => ‘block’, ‘operator’ => ‘==’, ‘value’ => ‘bit/qa-acf’]]],
]);
});
5-3. render.php(いつもの get_field() で書ける)
$question = get_field(‘question’);
$answer = get_field(‘answer’);
$explanation = get_field(‘explanation’);
if (!$question && !$answer) {
if ($is_preview) { echo ‘<p>Q&A:質問文と回答文を入力してください</p>’; }
return;
}
?>
<dl <?php echo get_block_wrapper_attributes([‘class’ => ‘qa’]); ?>>
<dt class=“qa__q”><?php echo esc_html($question); ?></dt>
<dd class=“qa__a”><?php echo nl2br(esc_html($answer)); ?></dd>
<?php if ($explanation) : ?>
<dd class=“qa__note”><?php echo nl2br(esc_html($explanation)); ?></dd>
<?php endif; ?>
</dl>
・必須(required)が効く:必須の欄が空のまま「公開」を押すと、「An ACF Block on this page requires attention before you can save.」と出て保存されません。
・入力値は本文の中に
"data":{"question":"…","_question":"field_bit_qa_question",…} の形で保存されます。投稿のカスタムフィールド(postmeta)には入らないので、WP_Query の meta_query で Q&A の中身を検索・並べ替えすることはできません。・
render.php は、エディタのプレビューと公開ページの両方で使われます($is_preview で切り分けられる)。
A・B・C のどれを選ぶか
| A. ビルドなし | B. ビルドあり | C. ACF ブロック | |
|---|---|---|---|
| 書く言語 | 素の JavaScript+PHP | JavaScript(JSX)+PHP | PHP だけ |
| ビルド環境 | 不要 | 必要(Node.js 22.22.2 以上) | 不要 |
| 入力する場所 | 本文の中で直接 | 本文の中で直接 | 右側のパネル(本文側はプレビュー) |
| 必要なプラグイン | なし | なし | ACF PRO(有料)か SCF(無料) |
| 向いている | まず作ってみたい・手早く作りたい | 入力のしやすさを作り込みたい | PHP 中心で作りたい・既に ACF PRO を使っている |
6. かんたんな書き方(作り方A)と公式の勧め(作り方B)は何が違うか
作り方A(ビルドなし)と作り方B(JSX+ビルド)は、できあがるブロックは同じです。今回の検証でも、挿入・入力・保存の形式・開き直し・公開ページの表示はまったく同じ結果になりました。違うのは「JavaScript の書き方」と「作るときの手間」だけです。
6-1. 同じところ
| 項目 | 作り方A・B 共通 |
|---|---|
| ブロックの定義 | block.json(API バージョン3・入力欄3つ) |
| 公開ページの表示 | render.php(PHP)。保存するのは入力値だけ(save は null) |
| 登録 | プラグインで register_block_type() |
| ブラウザで動く JavaScript | どちらも WordPress が読み込んでいる部品(window.wp.blocks など)を呼ぶ。作り方B のビルド後のファイルを開くと、import が window.wp.blocks・window.wp.blockEditor に置き換わっている(実物で確認) |
6-2. 違うところ
| 項目 | A. かんたんな書き方(ビルドなし) | B. 公式の勧め(JSX+ビルド) |
|---|---|---|
| JavaScript の書き方 | wp.element.createElement( 'div', … ) と関数で書く |
<div>…</div> と HTML に近い形(JSX)で書ける。入れ子が深くなっても読みやすい |
| 準備するもの | なし(テキストエディタだけ) | Node.js 22.22.2 以上と @wordpress/scripts(npm install で部品一式を手元に入れる) |
| 書いたあと | そのままサーバーに置く | npm run build で変換してから、build/ を置く |
使う部品の一覧(index.asset.php) |
手で書く。部品を増やしたら書き足す | ビルドが自動で作る(書き漏れがない) |
| ファイルの分け方 | 基本は1ファイル(index.js) |
edit.js・index.js などに分けて import でつなげる |
| CSS | 素の CSS を書く | SCSS で書ける。右から左に書く言語用の CSS(-rtl.css)も自動で作られる |
| サーバーに置く JavaScript | 書いたまま(今回 1,135 バイト) | 圧縮される(今回 1,826 バイト。ビルドの仕組みが付ける包みの分があるので、この規模では小さくはならない) |
| 向いている | 入力欄が数個の小さなブロック/まず作ってみたい/Node.js を用意したくない | 入力欄が多い・画面を作り込む/ブロックをいくつも作る/チームで作る |
6-3. どちらを選べばよいか
Q&A のように入力欄が数個のブロックなら、作り方A で困ることはありません。あとから「入力欄を増やしたい」「画面を作り込みたい」となったら、
block.json と render.php はそのまま使い、index.js だけを JSX で書き直して作り方B に移れます。
7. クラシックテーマ・Classic Editor の環境で使うには ★つまずきどころ
ブロックそのものは、クラシックテーマ(theme.json を持たない PHP テンプレートのテーマ)でも問題なく動きました。表示も CSS も出ます。つまずくのはテーマではなく、Classic Editor プラグインとカスタム投稿タイプの設定です。
7-1. Classic Editor が有効だと、ブロックエディタが開かない
Classic Editor 1.5 を有効にした状態で、投稿タイプごとにどちらのエディタになるかを測りました(faq は REST 有効のカスタム投稿タイプ、news_norest は REST 無効のカスタム投稿タイプ)。
| 設定(設定 → 投稿設定) | 投稿 | 固定ページ | faq | news_norest |
|---|---|---|---|---|
| 既定のまま(旧エディター・切り替え不可) | クラシック | クラシック | クラシック | クラシック |
| 「すべてのユーザーのデフォルトエディター」=ブロックエディター | ブロック | ブロック | ブロック | クラシック |
| 旧エディターのまま「ユーザーにエディターの切り替えを許可」=はい | ブロック※ | ブロック※ | ブロック※ | クラシック |
※ 切り替えを許可すると、投稿一覧に「編集 (ブロックエディター)」「編集 (旧エディター)」の両方が出て、記事ごとに選べるようになります。表は、記事ごとの選択がまだ無いときの判定です。
7-2. 「この投稿タイプだけブロックエディタに」は優先度に注意
「普段はクラシックのまま、Q&A を書く投稿タイプ(例:faq)だけブロックエディタにしたい」ときは、use_block_editor_for_post_type フィルターを使います。ただし優先度を省略して書くと効きません。
add_filter(‘use_block_editor_for_post_type’, function ($use, $post_type) {
return $post_type === ‘faq’ ? true : $use;
}, 10, 2);
// ○ 効く(優先度 101。迷ったらこれ)
add_filter(‘use_block_editor_for_post_type’, function ($use, $post_type) {
return $post_type === ‘faq’ ? true : $use;
}, 101, 2);
Classic Editor は、既定の設定(旧エディター・切り替え不可)のとき、同じフィルターに優先度 100 で「常に false」を差し込みます(
classic-editor.php の add_filter( 'use_block_editor_for_post_type', '__return_false', 100 ))。優先度 10 で true を返しても、あとから 100 で false に上書きされます。100 より大きい値にすると、Classic Editor のあとに実行されて効きます。実測でも 10 は全部クラシック、101 は faq だけブロックエディタになりました。なお、テーマの
functions.php に書くなら 100 でも効きます(実測)。同じ優先度なら登録した順に実行され、テーマはプラグインより後に読み込まれるためです。ただ、書く場所によって結果が変わるので、迷ったら 101 にしておくのが確実です。
7-3. カスタム投稿タイプは「REST API で表示」を有効に
上の表のとおり、show_in_rest が無効の投稿タイプ(news_norest)は、どの設定でもブロックエディタになりません。CPT UI で作った投稿タイプなら、編集画面の「REST API で表示(show_in_rest)」を「はい」にします。
7-4. 今までクラシックで書いた記事はどうなるか
ブロックエディタで開くと、本文全体が「クラシック」ブロック1つにまとめて表示されます。開いただけでは本文は1文字も変わらず、「未保存の変更あり」にもなりません(実測)。勝手にブロックへ変換されることはないので、既存記事への影響を心配せずに切り替えられます。
8. 作るときの注意点
JavaScript の
save() で HTML を保存する作り方もありますが、あとで HTML の形を変えると、既存の記事を編集画面で開いたときに「このブロックには、想定されていないか無効なコンテンツが含まれています。」と出ます(公開ページは古い形のまま表示され続けます。実測)。記事ごとに「復旧を試みる」を押して直すことになるので、記事が多いと大変です。今回のように save: () => null + render.php にしておけば、保存されるのは入力値だけなので、表示をあとから何度変えても既存記事は壊れません。
テーマの中に入れると、テーマを替えたときにブロックが消え、記事の中の Q&A が表示されなくなります。プラグインにしておけば、テーマを替えても残ります。
入力値をそのまま
echo しないこと。今回は、太字やリンクを使える欄は wp_kses_post()、文字だけの欄は esc_html() を通しています。<script> や onerror を入れても取り除かれることを確かめました。
block.json の "apiVersion" は 3 にします。2 以下(指定しない場合は 1)だと、WordPress 7.1.3 ではブラウザに次の警告が出ます(実測)。Block with API version 2 or lower is deprecated since version 6.9. … set the `apiVersion` field to 3 and test the block inside the iframe editor.(バージョン2以下は 6.9 から非推奨。将来はすべてのエディタを iframe で動かす予定なので、3 にして試してほしい、という内容)
3 にするときは、エディタ側で
useBlockProps() を使います。今回の作り方A・B はどちらも 3 で作っていて、警告は出ません。
「Q&A リスト」という親ブロックを作り、その中に Q&A ブロックを並べる形にすると、親でまとめて構造化データ(FAQPage)を出力するといった作り込みもできます(今回は未実装)。
9. まとめ(ご質問への回答)
block.json にブロックの名前と入力欄を書き、register_block_type() で登録するのが WordPress 標準の方法です。いちばん簡単なのは、Node.js もビルドも使わないビルドなしの作り方(3章・ファイル4つ)です。ほかに JSX で書いてビルドする作り方(4章)と、PHP だけで作る ACF ブロック(5章)があります。show_in_rest を有効にする必要があります(7章)。参考(一次ソース)
- Block API Reference(block.json / Metadata): https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/
- @wordpress/scripts: https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/
- ACF Blocks(ACF PRO の機能): https://www.advancedcustomfields.com/resources/blocks/
- Secure Custom Fields(WordPress.org): https://wordpress.org/plugins/secure-custom-fields/
- Classic Editor(WordPress.org): https://wordpress.org/plugins/classic-editor/
※本記事の動作は、WordPress 7.1.3(日本語版)/MySQL 5.7 の検証環境で、クラシックテーマ・Classic Editor 1.5・SCF 6.9.6・@wordpress/scripts 37.0.0(Node.js 24)を使って確認しています(2026年10月21日)。作り方A はビルドなしで同じ環境で確認しました。
作成: Seeds Brains(ジェイノーム業務支援AI)