Розробка користувацьких блоків Gutenberg для WordPress
Gutenberg замінив TinyMCE як основний редактор WordPress у версії 5.0. Користувацькі блоки — це React-компоненти, які відображаються і в редакторі, і на фронтенді. Вони дають редакторам візуальний контроль над структурованим вмістом без необхідності знати HTML. Розробка одного блока середної складності займає 2–4 дні.
Реєстрація блока
Сучасний підхід — block.json + JavaScript/PHP:
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "my-plugin/project-card",
"version": "1.0.0",
"title": "Карточка проекту",
"category": "common",
"icon": "portfolio",
"description": "Виводить карточку портфоліо-проекту з зображенням і описом",
"supports": {
"html": false,
"align": ["wide", "full"],
"color": { "background": true, "text": true }
},
"attributes": {
"projectId": { "type": "number" },
"showDescription": { "type": "boolean", "default": true },
"imageSize": { "type": "string", "default": "large" }
},
"editorScript": "file:./index.js",
"editorStyle": "file:./editor.css",
"style": "file:./style.css"
}
Реєстрація в PHP:
add_action('init', function () {
register_block_type(__DIR__ . '/blocks/project-card');
});
JavaScript: edit і save
import { registerBlockType } from '@wordpress/blocks';
import { useBlockProps, InspectorControls, MediaUpload } from '@wordpress/block-editor';
import { PanelBody, ToggleControl, SelectControl, Button } from '@wordpress/components';
import { useSelect } from '@wordpress/data';
registerBlockType('my-plugin/project-card', {
edit: ({ attributes, setAttributes }) => {
const { projectId, showDescription, imageSize } = attributes;
const blockProps = useBlockProps({ className: 'project-card-editor' });
const projects = useSelect(select =>
select('core').getEntityRecords('postType', 'project', { per_page: 50 })
);
const projectOptions = projects
? [{ label: '— виберіть проект —', value: 0 }, ...projects.map(p => ({ label: p.title.rendered, value: p.id }))]
: [{ label: 'Завантаження...', value: 0 }];
return (
<>
<InspectorControls>
<PanelBody title="Налаштування блока">
<SelectControl
label="Проект"
value={projectId}
options={projectOptions}
onChange={v => setAttributes({ projectId: Number(v) })}
/>
<ToggleControl
label="Показувати опис"
checked={showDescription}
onChange={v => setAttributes({ showDescription: v })}
/>
<SelectControl
label="Розмір зображення"
value={imageSize}
options={[
{ label: 'Thumbnail', value: 'thumbnail' },
{ label: 'Medium', value: 'medium' },
{ label: 'Large', value: 'large' },
]}
onChange={v => setAttributes({ imageSize: v })}
/>
</PanelBody>
</InspectorControls>
<div {...blockProps}>
{projectId
? <ProjectCardPreview projectId={projectId} showDescription={showDescription} />
: <p>Виберіть проект у панелі справа</p>
}
</div>
</>
);
},
save: () => null, // динамічний блок — рендер через PHP
});
save: () => null означає, що блок динамічний — контент рендерується PHP в момент запиту сторінки. Це переважно для блоків, дані яких змінюються (записи з БД).
PHP-рендеринг динамічного блока
register_block_type(__DIR__ . '/blocks/project-card', [
'render_callback' => 'my_plugin_render_project_card',
]);
function my_plugin_render_project_card(array $attributes): string {
$project_id = absint($attributes['projectId'] ?? 0);
$show_desc = (bool) ($attributes['showDescription'] ?? true);
$image_size = sanitize_key($attributes['imageSize'] ?? 'large');
if (!$project_id) return '';
$project = get_post($project_id);
if (!$project || $project->post_status !== 'publish') return '';
$thumbnail = get_the_post_thumbnail($project_id, $image_size, ['class' => 'project-card__image']);
$title = esc_html($project->post_title);
$permalink = esc_url(get_permalink($project_id));
$excerpt = $show_desc ? '<p class="project-card__desc">' . esc_html(get_the_excerpt($project)) . '</p>' : '';
$wrapper_attributes = get_block_wrapper_attributes(['class' => 'project-card']);
return "<article {$wrapper_attributes}>
{$thumbnail}
<h3 class=\"project-card__title\"><a href=\"{$permalink}\">{$title}</a></h3>
{$excerpt}
</article>";
}
get_block_wrapper_attributes() додає класи з supports.color та інші атрибути, які Gutenberg генерує автоматично.
Блок з innerBlocks
Блоки-контейнери приймають дочірні блоки через InnerBlocks:
import { InnerBlocks } from '@wordpress/block-editor';
const ALLOWED_BLOCKS = ['core/paragraph', 'core/heading', 'my-plugin/cta-button'];
const TEMPLATE = [
['core/heading', { level: 3, placeholder: 'Заголовок секції' }],
['core/paragraph', { placeholder: 'Опис...' }],
['my-plugin/cta-button', {}],
];
// У edit:
<InnerBlocks allowedBlocks={ALLOWED_BLOCKS} template={TEMPLATE} templateLock={false} />
// У save:
<InnerBlocks.Content />
Збірка
Блоки збираються через @wordpress/scripts:
{
"scripts": {
"build": "wp-scripts build",
"start": "wp-scripts start",
"lint:js": "wp-scripts lint-js"
},
"devDependencies": {
"@wordpress/scripts": "^27.0.0"
}
}
wp-scripts налаштований під WordPress за умовчанням: знає про @wordpress/* як про зовнішні залежності, генерує asset.php з хешем версії для коректної інвалідації кеша.
Розширення існуючих блоків через фільтри
Не завжди потребується новий блок — іноді достатньо додати атрибут або панель до існуючого:
import { addFilter } from '@wordpress/hooks';
import { createHigherOrderComponent } from '@wordpress/compose';
// Додаємо атрибут "data-section" до будь-якого блока
addFilter('blocks.registerBlockType', 'my-plugin/add-section-id', (settings) => {
settings.attributes = {
...settings.attributes,
sectionId: { type: 'string', default: '' },
};
return settings;
});
// Додаємо поле в InspectorControls
const withSectionIdControl = createHigherOrderComponent(BlockEdit => {
return (props) => {
const { attributes, setAttributes } = props;
return (
<>
<BlockEdit {...props} />
<InspectorControls>
<PanelBody title="Якорна ссилка">
<TextControl
label="ID секції"
value={attributes.sectionId}
onChange={v => setAttributes({ sectionId: v })}
/>
</PanelBody>
</InspectorControls>
</>
);
};
}, 'withSectionIdControl');
addFilter('editor.BlockEdit', 'my-plugin/section-id-control', withSectionIdControl);
Типові строки
Простий статичний блок з 2–3 атрибутами — 4–8 годин. Динамічний блок з PHP-рендером і панеллю налаштувань — 1–2 дні. Блок-контейнер з innerBlocks, власним стилем і server-side рендером — 2–4 дні. Набір з 5–10 пов'язаних блоків для дизайн-системи — від 2 тижнів.







