1. ホーム
  2. WordPress

【WordPress】block.json の parent・ancestor・allowedBlocks の使い方|ブロックを配置できる場所を制限する方法

Share

カスタムブロックを作っていると、「このブロックは、あのブロックの中でしか使ってほしくない」と思う場面が出てきます。カード用の見出しブロックが記事の本文にぽつんと置かれてしまったり、逆にカードの中に関係のないブロックを何でも入れられてしまったり。こうした配置のルールは、JavaScript を書かなくても block.json の parent・ancestor・allowedBlocks の3つで表現できます。この記事では、それぞれが何を制限するのか、どう使い分けるのかを、カードブロックの実例を交えて解説します。

ブロックの置き場所を決めておきたい理由

ブロックエディターは、InnerBlocks を使ったブロックの中に別のブロックを入れ子にできます。自由度が高いぶん、何も制限しなければ「どのブロックも、どこにでも置ける」状態になります。

これが困るのは、親子でセットになって初めて意味を持つブロックを作ったときです。たとえば「カード」ブロックの中で使う前提の「カード見出し」ブロックは、単体で本文に置かれても CSS が当たらず、見た目が崩れます。それでもインサーター(ブロック挿入ツール)には並んでしまうので、記事を書く人にとっては紛らわしい選択肢が増えるだけです。

WordPress のコアブロックも同じ考え方で作られています。カラムブロックの中に入る「カラム」(core/column)や、リストの中に入る「リスト項目」(core/list-item)は、親の外では使えないように設定されています。この仕組みを自分のブロックでも使うのが parent・ancestor・allowedBlocks です。

parent|直接の親ブロックを限定する

parent はブロック名の配列で、そのブロックを直接の子として受け入れる親ブロックを指定します。公式ドキュメントの表現を借りれば「指定したブロックの中に入れ子になっているときだけ、そのブロックが使えるようにする」設定です。

src/card-heading/block.json
{
	"$schema": "https://schemas.wp.org/trunk/block.json",
	"apiVersion": 3,
	"name": "webool/card-heading",
	"title": "カード見出し",
	"category": "design",
	"parent": [ "webool/card" ],
	"textdomain": "webool",
	"editorScript": "file:./index.js"
}

こう書くと、webool/card-heading は webool/card の直下にしか挿入できなくなります。配列なので、複数の親を許可することもできます。"parent": [ "webool/card", "webool/panel" ] と書けば、カードとパネルのどちらの直下でも使えます。

インサーターの見え方が変わる

parent を指定したブロックは、記事本文のトップレベルで「+」ボタンを押しても候補に出てきません。指定した親ブロックの内側にカーソルがあるとき、つまりそこに挿入できる状況のときだけインサーターに並びます。

これは不具合ではなく、意図したとおりの動作です。使える場所でだけ選択肢に出るので、記事を書く人が迷いません。裏を返すと、「ブロックを登録したのにインサーターで見つからない」というトラブルの多くは、この parent の指定が原因です。

もうひとつ覚えておきたいのは、parent が直接の親だけを見ている点です。カードの中にグループブロックを置き、そのグループの中にカード見出しを入れようとしても、直接の親がグループブロックなので許可されません。1段でも間に挟まると条件を満たさなくなる、という厳しさがあります。

ancestor|先祖のどこかにあればよい

その厳しさを緩めたのが ancestor です。WordPress 6.0 で追加されたプロパティで、指定したブロックの中であれば、何段深くても配置できるようにします。公式ドキュメントでは「指定したブロックタイプのサブツリーの、どの位置でもそのブロックを使えるようにする」と説明されています。

src/card-heading/block.json
{
	"$schema": "https://schemas.wp.org/trunk/block.json",
	"apiVersion": 3,
	"name": "webool/card-heading",
	"title": "カード見出し",
	"category": "design",
	"ancestor": [ "webool/card" ],
	"textdomain": "webool",
	"editorScript": "file:./index.js"
}

parent を ancestor に置き換えただけですが、意味は大きく変わります。カードの直下でも、カードの中のグループブロックの中でも、さらにその中のカラムブロックの中でも挿入できます。「カードのどこかにあること」だけが条件になるわけです。

公式ドキュメントが挙げている例が分かりやすいので紹介します。コメント本文のブロックをカラムブロックの中に置きたい、ただしそのカラムはコメントテンプレートブロックの中にある、というケースです。parent ではカラムが間に挟まった時点でアウトですが、ancestor なら「コメントテンプレートの子孫である」という条件を満たすので配置できます。レイアウト用のブロックを自由に挟めるようになるのが、ancestor の利点です。

どちらを使うかの判断は単純で、親子が必ず密着している構造なら parent、間にレイアウト用のブロックを挟ませたいなら ancestor です。カラムとカラム項目のように、間に何かを入れられると構造が壊れるものは parent が向いています。

allowedBlocks|自分の中に入れられる子ブロックを限定する

ここまでの2つは「自分がどこに置かれるか」を子ブロック側から宣言する設定でした。allowedBlocks は向きが逆で、自分の中に直接入れられる子ブロックを親ブロック側から指定します。WordPress 6.5 で block.json のプロパティとして追加されました。

src/card/block.json
{
	"$schema": "https://schemas.wp.org/trunk/block.json",
	"apiVersion": 3,
	"name": "webool/card",
	"title": "カード",
	"category": "design",
	"icon": "index-card",
	"description": "見出しと本文をまとめて表示するカードです。",
	"allowedBlocks": [ "webool/card-heading", "core/paragraph", "core/image" ],
	"textdomain": "webool",
	"editorScript": "file:./index.js",
	"style": "file:./style-index.css"
}

この指定により、カードブロックの中で使えるのはカード見出し・段落・画像の3つだけになります。カードの中にカーソルを置いてインサーターを開くと、この3つしか候補に出ません。ドキュメントでは「アコーディオンブロックが、直接の子としてアコーディオン見出しとアコーディオンパネルだけを許可する」といった使い方が例に挙げられています。

注意点は、効くのは直接の子だけということです。カードの中に段落を許可すれば段落は置けますが、許可したブロックがさらに子を持てる場合(グループブロックなど)、その中身までは allowedBlocks では制限できません。孫より深い階層まで縛りたいなら、そのブロック側に ancestor を書くか、あとで触れる templateLock を検討することになります。

InnerBlocks の allowedBlocks プロパティとの関係

allowedBlocks という名前は、InnerBlocks コンポーネントのプロパティとしても見たことがあるはずです。両者は別物ですが、目的は同じで、共存もします。公式ドキュメントでは、InnerBlocks の allowedBlocks プロパティについて「block.json の allowedBlocks に加えて、さらに直接の子として挿入できるブロックを絞り込める」と説明されています。つまり block.json 側が土台の制限で、コンポーネント側はそれをさらに狭める指定です。

そして「許可するブロックの一覧が常に同じなら、InnerBlocks のプロパティではなく block.json の allowedBlocks を使うほうがよい」とも書かれています。block.json に書いた情報はサーバー側にも登録されるため、エディターの JavaScript を読み込む前から制限が分かるからです。

逆に、ブロックの設定によって許可する子ブロックを変えたいときは、コンポーネント側のプロパティの出番です。次のコードは「テキストのみ」の設定がオンのときだけ、子ブロックを段落に絞り込む例です。

src/card/edit.js
import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor';

export default function Edit( { attributes } ) {
	const blockProps = useBlockProps( { className: 'webool-card' } );

	// textOnly が true のときだけ、さらに段落だけに絞り込む
	const innerBlocksProps = useInnerBlocksProps( blockProps, {
		allowedBlocks: attributes.textOnly ? [ 'core/paragraph' ] : undefined,
	} );

	return <div { ...innerBlocksProps } />;
}

3つのプロパティを整理する

似た役割なので混乱しやすいのですが、「どちらのブロックの block.json に書くか」と「何段目まで効くか」で分けて覚えると整理できます。

プロパティ書く場所意味階層対応バージョン
parent子ブロックの block.jsonこの親ブロックの中でだけ使える直接の親のみ—
ancestor子ブロックの block.jsonこの先祖ブロックの中なら使える何段深くてもよいWordPress 6.0.0
allowedBlocks親ブロックの block.jsonこの子ブロックだけを入れられる直接の子のみWordPress 6.5.0

値はいずれもブロック名の配列で、core/paragraph や webool/card のように名前空間を含めたフルネームで書きます。

そして3つは排他ではありません。子ブロックに parent、親ブロックに allowedBlocks の両方を書くと、「カード見出しはカードの中でしか使えない」「カードの中にはカード見出しなど決まったブロックしか入れられない」という双方向の制限になり、構造がいちばん堅くなります。カードのように構成が決まっているブロックでは、この組み合わせが基本形です。

実践例|カードブロックとカード見出しブロック

ここまでの内容を、実際に動く形にまとめます。作るのは2つのブロックです。「カード」は入れ物になる親ブロックで、中身は InnerBlocks に任せます。「カード見出し」はカードの中で使う見出し専用のブロックです。

親ブロック(カード)

親側では allowedBlocks で子ブロックを絞り込みます。block.json は先ほど示したものをそのまま使います。edit.js では useInnerBlocksProps でラッパー要素と入れ子領域をまとめ、template で初期状態の中身を用意しておきます。

src/card/edit.js
import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor';

// 挿入した直後に入っている中身
const TEMPLATE = [
	[ 'webool/card-heading', {} ],
	[ 'core/paragraph', { placeholder: 'カードの説明を入力' } ],
];

export default function Edit() {
	const blockProps = useBlockProps( { className: 'webool-card' } );

	// allowedBlocks は block.json 側に書いてあるのでここでは指定しない
	const innerBlocksProps = useInnerBlocksProps( blockProps, {
		template: TEMPLATE,
	} );

	return <div { ...innerBlocksProps } />;
}
src/card/save.js
import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor';

export default function save() {
	const blockProps = useBlockProps.save( { className: 'webool-card' } );
	const innerBlocksProps = useInnerBlocksProps.save( blockProps );

	return <div { ...innerBlocksProps } />;
}

子ブロック(カード見出し)

子ブロック側は parent でカードの中に閉じ込めます。見出しのテキストは RichText で編集できるようにしておきます。

src/card-heading/block.json
{
	"$schema": "https://schemas.wp.org/trunk/block.json",
	"apiVersion": 3,
	"name": "webool/card-heading",
	"title": "カード見出し",
	"category": "design",
	"parent": [ "webool/card" ],
	"attributes": {
		"content": {
			"type": "string",
			"source": "html",
			"selector": "h3",
			"default": ""
		}
	},
	"textdomain": "webool",
	"editorScript": "file:./index.js"
}
src/card-heading/edit.js
import { useBlockProps, RichText } from '@wordpress/block-editor';

export default function Edit( { attributes, setAttributes } ) {
	const blockProps = useBlockProps( { className: 'webool-card__heading' } );

	return (
		<RichText
			{ ...blockProps }
			tagName="h3"
			value={ attributes.content }
			onChange={ ( content ) => setAttributes( { content } ) }
			placeholder="カードの見出し"
		/>
	);
}

この状態でエディターを開くと、通常のインサーターには「カード」だけが表示されます。カードを挿入すると中にカード見出しと段落が最初から入っており、カード内で「+」を押したときの候補は allowedBlocks に書いた3つだけになります。子ブロック単体で本文に置かれる事故は起きません。

構成そのものを固定したいなら templateLock

「中身のブロックの種類も並び順も変えてほしくない」という場合は、parent や allowedBlocks とは別に templateLock を使います。InnerBlocks の templateLock に 'all' を渡すと、テンプレートが完全にロックされ、ブロックの追加・削除・並べ替えができなくなります。

src/card/edit.js
const innerBlocksProps = useInnerBlocksProps( blockProps, {
	template: TEMPLATE,
	templateLock: 'all', // 追加・削除・並べ替えを禁止する
} );

allowedBlocks が「何を入れられるか」の制限なのに対して、templateLock は「構成を変えられるか」の制限です。目的が違うので、必要に応じて併用してください。

インサーターにブロックが出てこないとき

この3つのプロパティを触り始めると、「登録したはずのブロックが見つからない」という状況によく出会います。原因はだいたい次のどれかです。

parent を書いたブロックは、親の外では表示されない

まず疑うべきはこれです。parent や ancestor を指定したブロックは、条件を満たす場所にカーソルがあるときしかインサーターに並びません。記事本文のトップレベルでいくら探しても出てこないのは正常な動作です。

確認するときは、先に親ブロックを挿入し、その内側にカーソルを置いてからインサーターを開いてください。それでも出ないなら、parent に書いたブロック名と、親ブロックの block.json の name が完全に一致しているかを見比べます。名前空間を省いて "parent": [ "card" ] のように書いてしまうと、どのブロックとも一致しません。

親側の allowedBlocks から漏れている

子ブロックの parent は正しくても、親ブロックの allowedBlocks にそのブロック名が入っていなければ、やはり挿入できません。両方を書いているときは 2つの条件の両方を満たす必要があるため、片方を書き換えたときにもう片方の更新を忘れると、どちらの視点から見ても正しそうなのに挿入できない、という状態になります。

InnerBlocks のプロパティでも allowedBlocks を指定している場合は、そちらがさらに絞り込みをかけている可能性もあります。block.json だけを見て悩まず、edit.js 側の指定も確認してください。

block.json の変更をビルドし直していない

@wordpress/scripts を使っている場合、src/ の block.json は build/ にコピーされ、PHP の register_block_type() が読むのはコピー後のファイルです。src/ だけを書き換えてビルドしていなければ、当然ながら設定は反映されません。npm run build を実行するか、npm run start で監視状態にしておきましょう。

ビルドしたのに変わらないときは、ブラウザのキャッシュも疑ってください。ブロックの定義はエディターの JavaScript として配信されるため、古いファイルが残っているとインサーターの挙動も古いままです。編集画面でスーパーリロード(Shift を押しながら再読み込み)を試すと切り分けられます。

WordPress のバージョンが足りていない

ancestor は WordPress 6.0、block.json の allowedBlocks は WordPress 6.5 で追加されたプロパティです。これより古い環境では、書いてあっても単に無視されます。エラーにはならず、制限が効かないだけなので気づきにくいのが厄介なところです。

配布するプラグインやテーマで古い環境も想定するなら、block.json の "requiresAtLeast" に必要なバージョンを明記しておくと親切です。どうしても 6.5 未満で子ブロックを制限したい場合は、InnerBlocks の allowedBlocks プロパティで代用できます。

まとめ

parent は子ブロック側に書き、直接の親を限定します。ancestor も子ブロック側に書きますが、こちらは先祖のどこかに指定のブロックがあれば配置できるため、間にレイアウト用のブロックを挟めます。allowedBlocks だけは親ブロック側に書き、自分の直下に入れられる子ブロックを指定します。「どちらの block.json に書くか」を意識すると、役割の違いがはっきりします。

カードのように構成が決まっているブロックでは、子に parent、親に allowedBlocks を両方書くのが基本です。許可するブロックが固定なら block.json の allowedBlocks を使い、ブロックの設定によって変えたいときだけ InnerBlocks の allowedBlocks プロパティで絞り込みます。

設定したあとにブロックがインサーターから消えたとしても、多くはそれが正しい動作です。親ブロックの内側にカーソルを置いて確認し、それでも出ないならブロック名の綴りとビルドのやり直しを疑ってください。ancestor は WordPress 6.0 以降、allowedBlocks は 6.5 以降という対応バージョンも、あわせて頭に入れておくと安心です。

参考ページ