2026年5月19日

2026年5月19日

Gutenbergのカスタムブロックを作る方法|create-blockとblock.jsonで画像付きカードを開発

WordPress のブロックエディター(Gutenberg)では、独自のブロックをプラグインとして追加できます。商品カードや CTA ボタンのように、決まった形の部品を編集者に入力欄つきで提供したいときに使います。

この記事では、公式のひな形作成ツール @wordpress/create-block でプラグインを作り、画像・見出し・本文・ボタンを並べた「カード」ブロックを追加する手順を説明します。コードは2026年10月時点の @wordpress/scripts 37 でビルドできることを確認しています。

必要なもの

  • WordPress 6.6 以降(この記事のブロックは apiVersion 3 と、ビルドで使われる react-jsx-runtime を前提にしています)
  • Node.js と npm。@wordpress/scripts 37 が対応する Node.js は 22(22.22.2 以降)または 24(24.15.0 以降)です
  • 開発用の WordPress(ローカル環境など)。本番サイトで直接開発しないでください
node -v
npm -v

ステップ1: create-block でプラグインのひな形を作る

WordPress の wp-content/plugins ディレクトリーで次のコマンドを実行します。

cd wp-content/plugins
npx @wordpress/create-block@latest my-custom-blocks

my-custom-blocks ディレクトリーに、サンプルのブロックが1つ入ったプラグインが作られます。主なファイルは次のとおりです。

my-custom-blocks/
├── my-custom-blocks.php      … プラグイン本体(ブロックの登録)
├── package.json              … ビルド用のスクリプト
├── src/my-custom-blocks/     … サンプルのブロックのソース
│   ├── block.json
│   ├── index.js
│   ├── edit.js
│   ├── save.js
│   ├── editor.scss
│   ├── style.scss
│   └── view.js
└── build/                    … ビルド結果(WordPress が読み込むのはこちら)

package.json には、次のスクリプトが用意されています。

コマンド 内容
npm start ファイルの変更を監視して、自動でビルドし直す(開発中に使う)
npm run build 本番用に圧縮してビルドする
npm run lint:js JavaScript の書き方を検査する
npm run plugin-zip 配布用の zip ファイルを作る

プラグイン本体の my-custom-blocks.php では、build ディレクトリーにあるブロックをまとめて登録しています。

function create_block_my_custom_blocks_block_init() {
	wp_register_block_types_from_metadata_collection( __DIR__ . '/build', __DIR__ . '/build/blocks-manifest.php' );
}
add_action( 'init', 'create_block_my_custom_blocks_block_init' );

wp_register_block_types_from_metadata_collection() は WordPress 6.8 で追加された関数で、npm run build が作る build/blocks-manifest.php を使って、src の下にあるすべてのブロックを登録します。このため、次のステップで src/card/ を追加すれば、PHP を書き換えなくてもカードブロックが登録されます。

ステップ2: block.json でブロックを定義する

src/card/ ディレクトリーを作り、block.json を置きます。ブロックの名前、表示名、属性(保存するデータ)、対応する機能、読み込むファイルをここで定義します。

{
	"$schema": "https://schemas.wp.org/trunk/block.json",
	"apiVersion": 3,
	"name": "my-blocks/card",
	"version": "1.0.0",
	"title": "カード",
	"category": "design",
	"icon": "id-alt",
	"description": "画像・見出し・本文・ボタンを並べたカード",
	"keywords": [ "card", "カード" ],
	"textdomain": "my-custom-blocks",
	"attributes": {
		"mediaId": { "type": "number" },
		"mediaUrl": { "type": "string", "default": "" },
		"mediaAlt": { "type": "string", "default": "" },
		"title": { "type": "string", "default": "" },
		"content": { "type": "string", "default": "" },
		"buttonText": { "type": "string", "default": "詳しく見る" },
		"buttonUrl": { "type": "string", "default": "" }
	},
	"supports": {
		"html": false,
		"align": [ "wide", "full" ],
		"color": { "background": true, "text": true },
		"spacing": { "padding": true }
	},
	"editorScript": "file:./index.js",
	"editorStyle": "file:./index.css",
	"style": "file:./style-index.css"
}
  • name は「名前空間/ブロック名」の形で、ほかのプラグインと重ならない名前にします。
  • attributes に source を書かない属性は、投稿の本文の中のブロックのコメント(<!-- wp:my-blocks/card {...} -->)に JSON で保存されます。
  • supports の color と spacing を有効にすると、背景色・文字色・余白の設定欄がサイドバーに自動で表示され、保存時のクラスやスタイルも WordPress が付けます。自分で色の属性や設定パネルを作る必要はありません。
  • editorScript・editorStyle・style の file:./ は、ビルド後の build/card/ の中のファイルを指します。index.js で読み込んだ style.scss は style-index.css に、editor.scss は index.css になります。

ステップ3: index.js でブロックを登録する

src/card/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,
} );

style.scss と editor.scss は、ここで import しないとビルドされず、CSS ファイルが作られません。

ステップ4: edit.js でエディターの画面を作る

edit は、エディターの中でブロックがどう表示され、どう編集されるかを決める関数です。src/card/edit.js に書きます。

import { __ } from '@wordpress/i18n';
import {
	useBlockProps,
	RichText,
	MediaUpload,
	MediaUploadCheck,
	InspectorControls,
} from '@wordpress/block-editor';
import { Button, PanelBody, TextControl } from '@wordpress/components';

export default function Edit( { attributes, setAttributes } ) {
	const { mediaId, mediaUrl, mediaAlt, title, content, buttonText, buttonUrl } =
		attributes;

	// className は useBlockProps に渡す(JSX で上書きしない)
	const blockProps = useBlockProps( { className: 'my-card' } );

	return (
		<>
			<InspectorControls>
				<PanelBody title={ __( 'ボタンのリンク', 'my-custom-blocks' ) }>
					<TextControl
						__next40pxDefaultSize
						__nextHasNoMarginBottom
						type="url"
						label={ __( 'リンク先 URL', 'my-custom-blocks' ) }
						value={ buttonUrl }
						onChange={ ( value ) => setAttributes( { buttonUrl: value } ) }
					/>
				</PanelBody>
			</InspectorControls>

			<div { ...blockProps }>
				<MediaUploadCheck>
					<MediaUpload
						allowedTypes={ [ 'image' ] }
						value={ mediaId }
						onSelect={ ( media ) =>
							setAttributes( {
								mediaId: media.id,
								mediaUrl: media.url,
								mediaAlt: media.alt,
							} )
						}
						render={ ( { open } ) =>
							mediaUrl ? (
								<Button onClick={ open } className="my-card__image-button">
									<img className="my-card__image" src={ mediaUrl } alt={ mediaAlt } />
								</Button>
							) : (
								<Button variant="secondary" onClick={ open }>
									{ __( '画像を選択', 'my-custom-blocks' ) }
								</Button>
							)
						}
					/>
				</MediaUploadCheck>

				<div className="my-card__body">
					<RichText
						tagName="h3"
						value={ title }
						onChange={ ( value ) => setAttributes( { title: value } ) }
						placeholder={ __( '見出しを入力', 'my-custom-blocks' ) }
						allowedFormats={ [ 'core/bold', 'core/italic' ] }
					/>
					<RichText
						tagName="p"
						value={ content }
						onChange={ ( value ) => setAttributes( { content: value } ) }
						placeholder={ __( '本文を入力', 'my-custom-blocks' ) }
					/>
					<RichText
						tagName="span"
						className="my-card__button"
						value={ buttonText }
						onChange={ ( value ) => setAttributes( { buttonText: value } ) }
						allowedFormats={ [] }
					/>
				</div>
			</div>
		</>
	);
}

使っている部品の役割は次のとおりです。

部品 役割
useBlockProps ブロックの外側の要素に必要な属性(クラス名、選択状態、色や余白の設定など)を返す。ブロックの外側の要素に展開して使う
RichText 太字やリンクを付けられる文字の入力欄
MediaUpload / MediaUploadCheck メディアライブラリを開いて画像を選ぶ。MediaUploadCheck はアップロード権限のないユーザーには中身を表示しない
InspectorControls 右側のサイドバー(ブロックの設定)に表示する欄
setAttributes 属性を更新する。更新した値が保存される

クラス名は useBlockProps( { className: 'my-card' } ) のように渡します。<div { ...blockProps } className="my-card"> と書くと、useBlockProps が付けたクラス名(wp-block-my-blocks-card など)が上書きされて消え、スタイルやエディターの動作がおかしくなります。

ステップ5: save.js で保存する HTML を決める

save は、投稿を保存したときに本文に書き込まれる HTML を返す関数です。src/card/save.js に書きます。

import { useBlockProps, RichText } from '@wordpress/block-editor';

export default function save( { attributes } ) {
	const { mediaUrl, mediaAlt, title, content, buttonText, buttonUrl } = attributes;
	const blockProps = useBlockProps.save( { className: 'my-card' } );

	return (
		<div { ...blockProps }>
			{ mediaUrl && (
				<img className="my-card__image" src={ mediaUrl } alt={ mediaAlt } />
			) }
			<div className="my-card__body">
				<RichText.Content tagName="h3" value={ title } />
				<RichText.Content tagName="p" value={ content } />
				{ buttonUrl && (
					<a className="my-card__button" href={ buttonUrl }>
						<RichText.Content value={ buttonText } />
					</a>
				) }
			</div>
		</div>
	);
}

save では useBlockProps ではなく useBlockProps.save を、RichText ではなく RichText.Content を使います。save の中では、属性の値から HTML を作るだけにし、日時や乱数のように毎回変わる値や、API から取ってくるデータは使いません(後述のブロックの検証で失敗するため)。

ステップ6: スタイルを書く

src/card/style.scss は、エディターとサイトの表示の両方で読み込まれます。

.wp-block-my-blocks-card {
	border: 1px solid #ddd;
	border-radius: 8px;
	overflow: hidden;

	.my-card__image {
		display: block;
		width: 100%;
		height: 200px;
		object-fit: cover;
	}

	.my-card__body {
		padding: 1.25rem;
	}

	.my-card__button {
		display: inline-block;
		padding: 0.5rem 1.25rem;
		border-radius: 4px;
		background: #1e1e1e;
		color: #fff;
		text-decoration: none;
	}
}

src/card/editor.scss は、エディターの中だけで読み込まれます。画像を押して選び直せるように包んだボタンの見た目を整えます。

.wp-block-my-blocks-card .my-card__image-button {
	display: block;
	width: 100%;
	height: auto;
	padding: 0;
}

.wp-block-my-blocks-card は、ブロック名 my-blocks/card から WordPress が自動で付けるクラス名です(wp-block-名前空間-ブロック名)。

ステップ7: ビルドして有効化する

cd wp-content/plugins/my-custom-blocks

# 開発中(変更を監視して自動でビルド)
npm start

# 公開用のビルド
npm run build

ビルドすると、build/card/ に block.json・index.js・index.asset.php・index.css・style-index.css などが作られ、build/blocks-manifest.php にカードブロックが追加されます。index.asset.php には、ブロックが使う WordPress のスクリプト(wp-blocks・wp-block-editor・wp-components など)が自動で書き込まれ、登録時に読み込まれます。

管理画面の「プラグイン」で「My Custom Blocks」を有効化するか、WP-CLI で有効化します。

wp plugin activate my-custom-blocks

ブロックが登録されたかは、投稿の編集画面でブロックを追加するときに「カード」を検索して確かめます。WP-CLI で確かめる場合は次のようにします(登録されていれば bool(true))。

wp eval 'var_dump( WP_Block_Type_Registry::get_instance()->is_registered( "my-blocks/card" ) );'

サンプルのブロック(src/my-custom-blocks/)が不要なら、そのディレクトリーを削除してからビルドし直します。

1つのブロックだけを登録する場合

WordPress 6.8 より前にも対応させたい場合や、ブロックごとに登録を書き分けたい場合は、register_block_type() に build の中のディレクトリーを渡します。

<?php
/**
 * Plugin Name:       My Custom Blocks
 * Description:       カードブロックを追加するプラグイン
 * Version:           1.0.0
 * Requires at least: 6.6
 * Requires PHP:      7.4
 * Text Domain:       my-custom-blocks
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

function my_custom_blocks_register() {
	register_block_type( __DIR__ . '/build/card' );
}
add_action( 'init', 'my_custom_blocks_register' );

save を変更するときの注意(ブロックの検証)

ブロックエディターは、投稿を開くたびに、保存されている HTML と、今の save が属性から作る HTML を比べます。一致しないと「このブロックには、想定されていないか無効なコンテンツが含まれています」という趣旨のエラーが表示され、ブロックを編集できなくなります。

すでに使われているブロックの save を変えるときは、変更前の save と属性を deprecated に残します。

import { registerBlockType } from '@wordpress/blocks';
import Edit from './edit';
import save from './save';
import saveV1 from './deprecated/save-v1';
import metadata from './block.json';

registerBlockType( metadata.name, {
	edit: Edit,
	save,
	deprecated: [
		{
			attributes: metadata.attributes,
			save: saveV1,
		},
	],
} );

./deprecated/save-v1.js には変更前の save をそのまま移します。属性の定義も変えた場合は、attributes に変更前の定義を書きます。deprecated の中の save で古い HTML が一致すると、そのブロックは読み込まれ、次に保存したときに新しい save の HTML に置き換わります。

PHP で表示を作る(ダイナミックブロック)

最新の投稿一覧のように、表示するたびに内容が変わるブロックは、HTML を保存せず PHP で出力します。block.json に render を書き、save は null を返すようにします。

{
	"render": "file:./render.php"
}
<?php
// render.php: $attributes(属性)、$content、$block が使える
?>
<div <?php echo get_block_wrapper_attributes(); ?>>
	<?php echo esc_html( $attributes['title'] ?? '' ); ?>
</div>

ひな形を作るときに npx @wordpress/create-block@latest my-dynamic-block --variant dynamic とすると、この形のブロックが作られます。

まとめ

  • npx @wordpress/create-block@latest でプラグインとビルド環境のひな形を作る
  • block.json で名前・属性・supports・読み込むファイルを定義する。色や余白は supports で付けられる
  • edit では useBlockProps・RichText・MediaUpload・InspectorControls で編集画面を作り、クラス名は useBlockProps に渡す
  • save では useBlockProps.save と RichText.Content を使い、属性だけから HTML を作る
  • npm run build でビルドし、プラグインを有効化する。save を変えるときは deprecated に旧版を残す

関連記事:

お気軽にご相談ください

お見積りへ お問い合わせへ
▲