2026年8月10日
2026年8月10日
WordPressのInteractivity APIでカスタムブロックを作る|REST APIからデータを取得する例
WordPress のブロックで、ボタンを押したら表示が変わる、サーバーからデータを読み込んで一覧を出す、といった動きを付けたいときは Interactivity API を使います。WordPress 6.5 から本体に入った、ブロックのフロントエンド(公開ページ)側の動きを書くための標準の仕組みです。
この記事では、「最新の投稿を読み込む」ボタンを押すと、WordPress の REST API から投稿を5件取得して一覧を表示するブロックを作ります。手順とコードは2026年10月時点の公式ドキュメントと雛形に合わせています。
Interactivity API の仕組み
Interactivity API は、次の2つを組み合わせて動きます。
| 場所 | 書くもの | 例 |
|---|---|---|
render.php(サーバー側の HTML) |
data-wp- で始まる属性(ディレクティブ)で、どの要素が何に反応するかを書く |
data-wp-on--click="actions.loadPosts" |
view.js(ブラウザー側) |
store() で状態(state)・アクション(actions)・コールバックを定義する |
actions.loadPosts の中身 |
data-wp-interactive="名前空間"を付けた要素とその子要素が、Interactivity API の対象になります。data-wp-contextは、その要素の範囲だけで使うデータ(コンテキスト)です。PHP ではwp_interactivity_data_wp_context()で出力します。- サーバーで最初の HTML がディレクティブどおりに描画されるため、JavaScript が動く前でも初期表示が出ます。
準備
- WordPress 6.5 以上(6.4 以前は Gutenberg プラグイン 17.5 以上が必要)
- Node.js と npm。2026年10月時点の
@wordpress/create-blockの説明では、Node.js 22.13.0 以上が必要とされています。 - ローカルの WordPress 環境(プラグインフォルダーで作業できること)
ステップ1: 雛形を作る
wp-content/plugins で次のコマンドを実行します。Interactivity API 用の公式テンプレートを使い、名前空間を myplugin にします。
cd wp-content/plugins
npx @wordpress/create-block@latest recent-posts-viewer --namespace=myplugin --template @wordpress/create-block-interactive-template
cd recent-posts-viewer
src/recent-posts-viewer/ に block.json・render.php・view.js・edit.js などが作られます。block.json には、テンプレートによって次の設定が入っています。
{
"supports": {
"interactivity": true
},
"viewScriptModule": "file:./view.js",
"render": "file:./render.php"
}
supports.interactivity: このブロックが Interactivity API を使うことを示します。viewScriptModule: 公開ページで読み込む JavaScript を、ES モジュールとして読み込みます。render: 公開ページの HTML を PHP で出力します(ダイナミックブロック)。
package.json のビルドコマンドにも、モジュールを扱うための --experimental-modules がテンプレートで付けられています。
ステップ2: render.php を書き換える
src/recent-posts-viewer/render.php を次の内容にします。REST API の URL は PHP で作って wp_interactivity_config() でブラウザー側に渡し、表示用のデータはコンテキストに持たせます。
<?php
/**
* 公開ページでの表示。$attributes, $content, $block が使える。
*/
wp_interactivity_config(
'myplugin',
array(
'postsUrl' => esc_url_raw(
add_query_arg(
array(
'per_page' => 5,
'_fields' => 'id,link,title',
),
rest_url( 'wp/v2/posts' )
)
),
)
);
$context = array(
'posts' => array(),
'loading' => false,
'error' => '',
);
?>
<div
<?php echo get_block_wrapper_attributes(); ?>
data-wp-interactive="myplugin"
<?php echo wp_interactivity_data_wp_context( $context ); ?>
>
<button
type="button"
data-wp-on--click="actions.loadPosts"
data-wp-bind--disabled="context.loading"
>
<?php esc_html_e( '最新の投稿を読み込む', 'recent-posts-viewer' ); ?>
</button>
<p data-wp-bind--hidden="!context.loading">
<?php esc_html_e( '読み込み中…', 'recent-posts-viewer' ); ?>
</p>
<p data-wp-bind--hidden="!context.error" data-wp-text="context.error"></p>
<ul>
<template data-wp-each--post="context.posts" data-wp-each-key="context.post.id">
<li><a data-wp-bind--href="context.post.link" data-wp-text="context.post.title"></a></li>
</template>
</ul>
</div>
使っているディレクティブは次のとおりです。
| ディレクティブ | 働き |
|---|---|
data-wp-on--click |
クリックされたらアクションを呼ぶ |
data-wp-bind--属性名 |
値に応じて HTML の属性を付け外しする(! を付けると真偽を反転) |
data-wp-text |
要素の中の文字を値で置き換える |
data-wp-each--名前 |
配列の要素ごとに <template> の中身を繰り返す。各要素は context.名前 で参照する |
data-wp-each-key |
繰り返す要素を見分けるキー。要素がオブジェクトのときは <template> に付ける |
?per_page= を URL の末尾に文字列で足すと、パーマリンクが「基本」のサイト(REST API の URL が ?rest_route= の形になる)で壊れます。add_query_arg() で組み立てておくと、どちらの形でも動きます。
ステップ3: view.js を書き換える
src/recent-posts-viewer/view.js を次の内容にします。
import { store, getContext, getConfig } from '@wordpress/interactivity';
// REST API の title.rendered は HTML 実体参照を含むことがあるので、文字に戻す
const decodeHtml = ( html ) =>
new DOMParser().parseFromString( html, 'text/html' ).documentElement.textContent;
store( 'myplugin', {
actions: {
*loadPosts() {
const context = getContext();
const { postsUrl } = getConfig( 'myplugin' );
context.loading = true;
context.error = '';
try {
const posts = yield fetch( postsUrl ).then( ( response ) => {
if ( ! response.ok ) {
throw new Error( `HTTP ${ response.status }` );
}
return response.json();
} );
context.posts = posts.map( ( post ) => ( {
id: post.id,
link: post.link,
title: decodeHtml( post.title.rendered ),
} ) );
} catch ( error ) {
context.error = '投稿を読み込めませんでした。';
} finally {
context.loading = false;
}
},
},
} );
要点は次のとおりです。
- 非同期のアクションはジェネレーター関数(
*loadPosts())で書き、awaitの代わりにyieldを使います。 公式ドキュメントでは、async/await ではなくジェネレーターを使うよう案内されています。yieldのあとでもgetContext()で得たコンテキストが正しいブロックのものとして扱われます。 contextの値を書き換えると、render.phpのディレクティブに従って表示が自動で更新されます。DOM を直接操作するコードは書きません。event.preventDefault()などイベントを同期的に扱うアクションは、WordPress 6.8 以降withSyncEvent()で包む必要があります。この例のアクションはイベントを使わないので不要です。
ステップ4: ビルドして動かす
npm run build
wp plugin activate recent-posts-viewer
WP-CLI を使わない場合は、管理画面の「プラグイン」で、作成したプラグインを有効化します。投稿か固定ページの編集画面でブロックを追加し、公開ページでボタンを押して一覧が出ることを確かめます。開発中は npm start にすると、ファイルを保存するたびにビルドされます。
ブロックエディターの中の表示は edit.js が担当し、Interactivity API の動きは公開ページでだけ働きます。エディターでも同じ一覧を見せたい場合は、edit.js 側で @wordpress/api-fetch や useSelect を使って別に書きます。
うまく動かないとき
| 症状 | 確かめること |
|---|---|
| ボタンを押しても何も起きない | data-wp-interactive と store() の名前空間(ここでは myplugin)が一致しているか。npm run build 後の build/ が読み込まれているか |
ブラウザーのコンソールに getConfig などの読み込みエラーが出る |
WordPress が 6.5 以上か。block.json が viewScript ではなく viewScriptModule になっているか |
| 「投稿を読み込めませんでした。」と出る | ブラウザーの開発者ツールのネットワーク欄で REST API の応答を確認する。REST API を制限するプラグインや設定で /wp/v2/posts が止められていないか |
文字の中に ’ などが出る |
title.rendered をそのまま入れていないか(例の decodeHtml() を通す) |
REST API がエラーを返す場合は WordPress REST APIのエラーの直し方 も参考にしてください。
外部の API を使う場合の注意
天気やニュースなど外部サービスの API を使う場合も、考え方は同じです。ただし次の点に気を付けます。
- API キーをブラウザー側に置かない:
view.jsやwp_interactivity_config()に書いた値は、訪問者が誰でも見られます。キーが必要な API は、PHP 側でwp_remote_get()を使って取得するか、自前の REST API エンドポイントを中継にします。 - 取得結果をキャッシュする: 外部 API には回数制限があることが多いため、PHP 側で取得した結果を Transients API などで一定時間保存し、ページごと・訪問者ごとに外部へ問い合わせないようにします。
- CORS: ブラウザーから別のドメインの API を直接呼ぶ場合、相手のサーバーが CORS を許可していないと読み込めません。この場合も PHP 側で取得するのが現実的です。
- 取得するデータを絞る: この例の
_fieldsのように、必要な項目だけを要求すると転送量が減ります。
まとめ
- ブロックのフロントエンドの動きは、WordPress 6.5 以降の Interactivity API で書けます。
- 雛形は
npx @wordpress/create-block@latest スラッグ --template @wordpress/create-block-interactive-templateで作ります。 render.phpにディレクティブ、view.jsにstore()を書き、非同期の処理はジェネレーター関数とyieldで書きます。- サーバー側で作った値は
wp_interactivity_config()やコンテキストで渡し、秘密の値はブラウザーに出さないようにします。