カスタムブロックで入力した値は、通常はブロックの属性(attributes)として投稿本文の中に保存されます。しかし「記事のサブタイトル」や「読了時間」のように、本文ではなく投稿そのものに紐づけたいデータもあります。こうした値の置き場所が投稿メタ(カスタムフィールド)で、ブロックエディターから読み書きするときに使うのが useEntityProp フックです。この記事では register_post_meta でのメタ登録から、useEntityProp での読み書き、フロント側への出力までを順番に解説します。
目次
useEntityProp は投稿の項目を state のように扱えるフック
useEntityProp は @wordpress/core-data パッケージが提供する React フックです。投稿・固定ページ・タクソノミーといった「エンティティ」の特定の項目を指定すると、現在の値と、それを更新する関数のペアを返してくれます。使い勝手は React の useState とほぼ同じです。
const [ meta, setMeta ] = useEntityProp( 'postType', 'post', 'meta' );
setMeta を呼ぶと、その変更はエディターの「未保存の編集」として記録されます。つまり投稿タイトルを書き換えたときと同じ扱いになり、公開ボタンや更新ボタンを押したタイミングで REST API 経由でまとめて保存されます。ブロック側で保存処理を書く必要はありません。
属性(attributes)との違いもここにあります。属性は投稿本文の HTML に埋め込まれるためブロックを消すと一緒に消えますが、メタは投稿に対して保存されるので、ブロックを削除しても値は残り、テンプレートや他のプラグインからも get_post_meta() で読み出せます。
register_post_meta でメタを登録する
ブロックエディターは REST API を通して投稿データをやり取りしています。そのため、扱いたいメタをあらかじめ register_post_meta() で登録し、show_in_rest を true にしておく必要があります。ここが抜けていると、あとで説明する useEntityProp からはメタが存在しないものとして扱われます。
function mytheme_register_post_meta() {
register_post_meta(
'post', // 対象の投稿タイプ('' ならすべて)
'mytheme_subtitle', // メタキー
array(
'type' => 'string', // string / number / boolean など
'single' => true, // 1つの値だけ持つ
'default' => '',
'show_in_rest' => true, // ブロックエディターから使うには必須
'auth_callback' => function () {
// 誰がこのメタを編集できるか
return current_user_can( 'edit_posts' );
},
)
);
}
add_action( 'init', 'mytheme_register_post_meta' );
single を true にすると値がそのまま返り、false(既定)のままだと配列で返ります。useEntityProp から扱うときは true にしておくほうが素直です。type は REST API 側でのバリデーションに使われるので、実際に入れる値の型と合わせてください。数値を入れるつもりで 'string' のまま送ると、型が違うとして保存が弾かれます。
登録先を投稿タイプ単位ではなくすべての投稿タイプにしたい場合は、第1引数に空文字 '' を渡します。また register_post_meta() は register_meta() の投稿用ラッパーで、内部で object_subtype に投稿タイプ名を渡しているだけです。
edit.js でメタを読み書きする
登録できたら、カスタムブロックの edit から読み書きします。useEntityProp の第2引数には投稿タイプ名が必要なので、core/editor ストアの getCurrentPostType() で編集中の投稿タイプを取得します。
import { useEntityProp } from '@wordpress/core-data';
import { useSelect } from '@wordpress/data';
import { useBlockProps } from '@wordpress/block-editor';
import { TextControl } from '@wordpress/components';
export default function Edit() {
const blockProps = useBlockProps();
// いま編集している投稿タイプ('post' や 'page')を取得する
const postType = useSelect(
( select ) => select( 'core/editor' ).getCurrentPostType(),
[]
);
// 投稿の meta を [値, 更新関数] の形で受け取る
const [ meta, setMeta ] = useEntityProp( 'postType', postType, 'meta' );
const subtitle = meta?.mytheme_subtitle ?? '';
return (
<div { ...blockProps }>
<TextControl
__nextHasNoMarginBottom
label="サブタイトル"
value={ subtitle }
onChange={ ( value ) =>
setMeta( { ...meta, mytheme_subtitle: value } )
}
/>
</div>
);
}
ポイントは setMeta の呼び方です。meta は投稿が持つすべてのメタをまとめたオブジェクトなので、{ ...meta, mytheme_subtitle: value } のように既存の値ごとスプレッドで展開してから、変更するキーだけを上書きします。この書き方が公式ドキュメントでも使われている形です。
また meta?.mytheme_subtitle とオプショナルチェーンを使っているのは、postType がまだ取得できていない最初のレンダリングで meta が undefined になりうるためです。ここを素で書くとエディターが真っ白になることがあるので、必ず保険をかけておきます。
@wordpress/core-data や @wordpress/components を import すると、@wordpress/scripts のビルドが依存関係を自動で検出し、build/index.asset.php に wp-core-data などを書き出してくれます。block.json で editorScript にビルド結果を指定していれば、読み込みの設定を手で書き足す必要はありません。
引数と戻り値
useEntityProp は4つの引数を取ります。第4引数まで指定する場面は限られますが、後述するテンプレート内での利用では重要になります。
| 引数 | 説明 |
|---|---|
kind | エンティティの種類。投稿や固定ページなら 'postType'、タクソノミーなら 'taxonomy' |
name | 種類の中での名前。'post' 'page' などの投稿タイプ名 |
prop | 扱う項目名。'meta' 'title' 'excerpt' 'status' など |
_id(省略可) | 対象のID。省略すると、いま編集中のエンティティが使われる |
戻り値は [ 値, 更新関数, 完全な値 ] の配列です。3つめは title や content のように raw と rendered の両方を持つ項目のために用意されており、1つめが編集用の生の値、3つめがオブジェクト全体になります。meta を扱うぶんには最初の2つだけで足ります。
prop にはメタ以外も指定できます。たとえば 'title' を渡せば、ブロックの中から投稿タイトルを読み書きできます。
// 投稿タイトルを読み書きする
const [ title, setTitle ] = useEntityProp( 'postType', postType, 'title' );
// 抜粋を読み書きする
const [ excerpt, setExcerpt ] = useEntityProp( 'postType', postType, 'excerpt' );
フロント側にメタの値を出力する
メタは投稿本文の HTML には含まれないため、save で書き出すことはできません。値をフロントに表示したい場合は動的ブロックにして、render.php の中で get_post_meta() から読み出します。
<?php
$subtitle = get_post_meta( get_the_ID(), 'mytheme_subtitle', true );
// 値が空なら何も出力しない
if ( '' === $subtitle ) {
return;
}
?>
<p <?php echo get_block_wrapper_attributes(); ?>>
<?php echo esc_html( $subtitle ); ?>
</p>
あわせて block.json に "render": "file:./render.php" を指定し、JavaScript 側の save は null を返すようにします(registerBlockType の save を省略した場合も同じ扱いです)。本文には空のブロックコメントだけが保存され、表示のたびに render.php が実行されます。
ブロックを置かずにテーマのテンプレートから直接出したい場合は、single.php などで同じく get_post_meta() を呼べば済みます。メタは投稿に紐づいているので、どこからでも取り出せるのが利点です。
メタが保存されない・undefined になるとき
show_in_rest が true になっていない
いちばん多い原因です。show_in_rest が false(既定)だと REST API のレスポンスに meta として含まれないため、useEntityProp から見ると存在しないキーになります。エディター上では入力できているように見えても、更新すると値が消えます。ブラウザーの開発者ツールで /wp-json/wp/v2/posts/<ID> を開き、meta の中に自分のキーがあるか確認すると切り分けが早いです。
アンダースコアで始まるキーを使っている
_mytheme_subtitle のようにアンダースコアで始まるメタキーは「保護されたメタ」として扱われ、標準では編集できません。この形式を使うなら auth_callback で編集権限を返すように指定するのが必須です。逆にいえば、特別な理由がなければアンダースコアを付けないほうがつまずきません。
postType が取得できない画面で呼んでいる
select( 'core/editor' ).getCurrentPostType() は「いま編集中の投稿」を前提にした関数なので、サイトエディターでテンプレートを編集しているときなどは意図した投稿タイプを返しません。この場合はブロックコンテキストを使い、block.json に "usesContext": [ "postType", "postId" ] を書いて、edit の context から受け取ります。
export default function Edit( { context: { postType, postId } } ) {
// 第4引数に投稿IDを渡して、対象を明示する
const [ meta, setMeta ] = useEntityProp(
'postType',
postType,
'meta',
postId
);
// ...
}
setMeta で他のメタを消してしまっている
setMeta( { mytheme_subtitle: value } ) のようにスプレッドを省くと、そのブロックが把握していない他のメタまで含めて置き換えるつもりの値を渡すことになります。プラグインが同じ投稿に別のメタを持たせている環境で事故につながるため、必ず { ...meta, キー: 値 } の形で書いてください。
まとめ
useEntityProp を使うと、投稿メタを useState と同じ感覚でブロックから読み書きできます。手順は、register_post_meta() で show_in_rest を true にしてメタを登録し、edit で useEntityProp( 'postType', postType, 'meta' ) を呼び、setMeta にスプレッドで値を渡す、という3ステップです。
本文に埋め込む値なら属性、投稿に紐づけて他の場所からも参照したい値ならメタ、と使い分けるのが基本です。メタに入れた値はテンプレートや render.php から get_post_meta() で取り出せるので、テーマ全体で再利用しやすくなります。