2026年5月19日

2026年5月19日

WordPressのGutenbergカスタムブロックをJavaScriptで開発する方法

はじめに

Gutenbergエディターでオリジナルのカスタムブロックを作成することで、コンテンツ編集者が使いやすいUIでオリジナルコンテンツを管理できるようになります。プラグインとして開発することで再利用性も高まります。

症状・原因

カスタムブロックが必要になる主なケース:

  • デザイナーが作ったレイアウトをブロックとして提供したい
  • ショートコードで実現していた機能をブロックに移行したい
  • 商品カード・CTAボタンなど繰り返し使うUIを統一したい
  • ACFフィールドとGutenbergを組み合わせたい

解決手順

ステップ1:開発環境をセットアップする

# Node.jsが必要(v16以上推奨)
node -v

# プラグインディレクトリを作成
mkdir wp-content/plugins/my-custom-blocks
cd wp-content/plugins/my-custom-blocks

# @wordpress/scripts でビルド環境をセットアップ
npm init -y
npm install --save-dev @wordpress/scripts

# package.json のscriptsを追加
# "scripts": {
#   "build": "wp-scripts build",
#   "start": "wp-scripts start",
#   "lint:js": "wp-scripts lint-js"
# }

プラグインのPHPファイルを作成します:

<?php
/**
 * Plugin Name: My Custom Blocks
 * Description: カスタムGutenbergブロック集
 * Version: 1.0.0
 */

// ブロックを登録
add_action('init', 'register_my_custom_blocks');
function register_my_custom_blocks() {
    // block.jsonがある場合はこれだけでOK
    register_block_type(__DIR__ . '/build/card-block');
}

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

src/card-block/block.jsonを作成します:

{
    "$schema": "https://schemas.wp.org/trunk/block.json",
    "apiVersion": 3,
    "name": "my-blocks/card",
    "version": "1.0.0",
    "title": "カードブロック",
    "category": "design",
    "icon": "format-image",
    "description": "画像・タイトル・テキストのカードレイアウト",
    "keywords": ["card", "カード", "レイアウト"],
    "attributes": {
        "title": {
            "type": "string",
            "default": ""
        },
        "content": {
            "type": "string",
            "default": ""
        },
        "imageUrl": {
            "type": "string",
            "default": ""
        },
        "imageAlt": {
            "type": "string",
            "default": ""
        },
        "buttonText": {
            "type": "string",
            "default": "詳しく見る"
        },
        "buttonUrl": {
            "type": "string",
            "default": ""
        },
        "backgroundColor": {
            "type": "string",
            "default": "#ffffff"
        }
    },
    "supports": {
        "html": false,
        "align": ["wide", "full"]
    },
    "editorScript": "file:./index.js",
    "editorStyle": "file:./index.css",
    "style": "file:./style-index.css",
    "viewScript": "file:./view.js"
}

ステップ3:edit関数でエディター内UIを実装する

src/card-block/index.jsを作成します:

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

registerBlockType(metadata.name, {
    edit: function Edit({ attributes, setAttributes }) {
        const { title, content, imageUrl, imageAlt, buttonText, buttonUrl, backgroundColor } = attributes;
        const blockProps = useBlockProps({
            style: { backgroundColor },
        });
        
        return (
            <>
                {/* サイドバーの設定パネル */}
                <InspectorControls>
                    <PanelBody title="カード設定" initialOpen={true}>
                        <TextControl
                            label="ボタンURL"
                            value={buttonUrl}
                            onChange={(val) => setAttributes({ buttonUrl: val })}
                            placeholder="https://example.com"
                        />
                    </PanelBody>
                    <PanelColorSettings
                        title="カラー設定"
                        colorSettings={[{
                            value: backgroundColor,
                            onChange: (color) => setAttributes({ backgroundColor: color }),
                            label: '背景色',
                        }]}
                    />
                </InspectorControls>
                
                {/* エディター内プレビュー */}
                <div {...blockProps} className="my-card-block">
                    <MediaUploadCheck>
                        <MediaUpload
                            onSelect={(media) => setAttributes({
                                imageUrl: media.url,
                                imageAlt: media.alt,
                            })}
                            allowedTypes={['image']}
                            render={({ open }) => (
                                <div className="my-card-image-wrap">
                                    {imageUrl ? (
                                        <img
                                            src={imageUrl}
                                            alt={imageAlt}
                                            onClick={open}
                                            style={{ cursor: 'pointer', width: '100%' }}
                                        />
                                    ) : (
                                        <Button
                                            onClick={open}
                                            variant="secondary"
                                            icon="upload"
                                        >
                                            {__('画像を選択')}
                                        </Button>
                                    )}
                                </div>
                            )}
                        />
                    </MediaUploadCheck>
                    
                    <div className="my-card-body">
                        <RichText
                            tagName="h3"
                            value={title}
                            onChange={(val) => setAttributes({ title: val })}
                            placeholder="タイトルを入力..."
                            allowedFormats={['core/bold', 'core/italic']}
                        />
                        <RichText
                            tagName="p"
                            value={content}
                            onChange={(val) => setAttributes({ content: val })}
                            placeholder="本文を入力..."
                        />
                        <RichText
                            tagName="span"
                            className="my-card-button"
                            value={buttonText}
                            onChange={(val) => setAttributes({ buttonText: val })}
                        />
                    </div>
                </div>
            </>
        );
    },
    
    save: function Save({ attributes }) {
        const { title, content, imageUrl, imageAlt, buttonText, buttonUrl, backgroundColor } = attributes;
        const blockProps = useBlockProps.save({
            style: { backgroundColor },
        });
        
        return (
            <div {...blockProps} className="my-card-block">
                {imageUrl && (
                    <div className="my-card-image-wrap">
                        <img src={imageUrl} alt={imageAlt} />
                    </div>
                )}
                <div className="my-card-body">
                    <RichText.Content tagName="h3" value={title} />
                    <RichText.Content tagName="p" value={content} />
                    {buttonUrl && (
                        <a href={buttonUrl} className="my-card-button">
                            <RichText.Content tagName="span" value={buttonText} />
                        </a>
                    )}
                </div>
            </div>
        );
    },
});

ステップ4:スタイルを追加する

src/card-block/style.scss(フロントエンド + エディター共通):

.my-card-block {
    border: 1px solid #e0e0e0;
    border-radius: 8px;
    overflow: hidden;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    
    .my-card-image-wrap img {
        width: 100%;
        height: 200px;
        object-fit: cover;
        display: block;
    }
    
    .my-card-body {
        padding: 1.5rem;
        
        h3 {
            margin: 0 0 0.75rem;
            font-size: 1.25rem;
            color: #1d2327;
        }
        
        p {
            color: #666;
            line-height: 1.6;
            margin-bottom: 1rem;
        }
    }
    
    .my-card-button {
        display: inline-block;
        background: #0073aa;
        color: #fff;
        padding: 0.5rem 1.25rem;
        border-radius: 4px;
        text-decoration: none;
        font-weight: bold;
        
        &:hover {
            background: #005d8c;
        }
    }
}

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

# 開発ビルド(ウォッチモード)
npm run start

# 本番ビルド
npm run build

# ビルド後の構成
# build/
# ├── card-block/
# │   ├── index.js
# │   ├── index.css
# │   ├── style-index.css
# │   └── block.json
# WP-CLIでプラグインを有効化
wp plugin activate my-custom-blocks

# ブロックが登録されているか確認
wp block list | grep my-blocks

注意事項

  • save関数を変更すると、既存のブロックコンテンツがバリデーションエラーになります。変更時はdeprecationsを使って後方互換性を保ってください
  • React/JSXを使用するため@wordpress/scriptsのビルド環境が必要です
  • PHPサーバーサイドレンダリングが必要なブロックはrender_callbackを使用してください

まとめ

Gutenbergカスタムブロック開発は、①@wordpress/scripts環境構築、②block.jsonでのメタ定義、③edit関数でのエディターUI、④save関数でのフロントエンド出力、⑤ビルドと有効化の流れで実装します。RichTextMediaUploadInspectorControlsを組み合わせることで、直感的な編集UIを作れます。

お気軽にご相談ください

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