- Quick creation of blocks and panels (cpt fields in the sidebar) based on WP React components (excluding DNB kit for repeater)
- Nice UI - you can place components in the editor and/or sidebar (block options)
- Post meta support (save block data in postmeta)
- Conditional logic for components (show/hide some fields based on value from other)
- Conditional logic for assets
- Assets are present on the page only if there is the block
- External assets
- Builder for adding block options/fields
- You don’t need to know React
- Text
- Textarea
- Toggle
- Select
- ColorPalette
- ColorPicker
- TimePicker (with date)
- Range
- RichText
- Image
- Link
- Message (just some title with text)
- Section
- Repeater (based on @dnd-kit)
- Install gutengood package using composer
composer require yarovikov/gutengood
- Install gutengood-js
yarn add gutengood-js
- Import gutengood js and css
add this in editor.js
import 'gutengood-js/build/gutengood';
add this in editor.css
@import "../../node_modules/gutengood-js/build/gutengood.css";
- Run
yarn build
- Enqueue editor assets if you don't have this in your setup.php
add_action('enqueue_block_editor_assets', function (): void {
bundle('editor')->enqueue();
}, 100);
Please follow the standard project structure:
![](https://private-user-images.githubusercontent.com/30932012/399706017-1dc5e8d3-8f59-4a3b-b330-4930be7acc43.jpg?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkyMjEyMzIsIm5iZiI6MTczOTIyMDkzMiwicGF0aCI6Ii8zMDkzMjAxMi8zOTk3MDYwMTctMWRjNWU4ZDMtOGY1OS00YTNiLWIzMzAtNDkzMGJlN2FjYzQzLmpwZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAyMTAlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMjEwVDIwNTUzMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWU0MTBmYzIxOWU1MWRiZTUxZDA4ZTM5NDMyMDViYmRkN2U3OTJkMzcxZjY2ZTc4YWNmM2I0OTE0NTNhMTRmYTEmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.HOEZfbqQmKbkETxRCbGZsZyjd7JVm0CXrptb5i8jCQA)
![](https://private-user-images.githubusercontent.com/30932012/356887690-db5d6c99-317f-4800-99c3-77617e71a4b0.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkyMjEyMzIsIm5iZiI6MTczOTIyMDkzMiwicGF0aCI6Ii8zMDkzMjAxMi8zNTY4ODc2OTAtZGI1ZDZjOTktMzE3Zi00ODAwLTk5YzMtNzc2MTdlNzFhNGIwLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAyMTAlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMjEwVDIwNTUzMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWY0MDkwNTkwZmJiMDE2YjJlZTlhOTc0Njg0NzI5YjY3Yzc4OGQ4MjNlOTljMTc1MjIzNGUzN2RmNTU4Mjc2YjMmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.ClWec0NcnvW60LkOWEC1jl0Apm-f1xxgLxX1aT3_WBw)
Use Edit Button to see editable components in the block
![](https://private-user-images.githubusercontent.com/30932012/356888175-381f549c-89aa-4bc0-bc8a-6dc0319d0411.jpg?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkyMjEyMzIsIm5iZiI6MTczOTIyMDkzMiwicGF0aCI6Ii8zMDkzMjAxMi8zNTY4ODgxNzUtMzgxZjU0OWMtODlhYS00YmMwLWJjOGEtNmRjMDMxOWQwNDExLmpwZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAyMTAlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMjEwVDIwNTUzMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTc1YmI1MmRhYzBiYmQ2YjdmNmRlOTI3MjdkYTM5ZWQyYTVhNzkxNjA5ZjdiNjIxNzgwNWQ2YjVjYzc4M2JkNzEmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.zoEIrSj39Sk6gctHg730QDXuH_PhhOLNTXlDF2zcWyY)
![](https://private-user-images.githubusercontent.com/30932012/356888531-d8466f71-8610-4dd7-86d9-a898912f4b2d.jpg?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkyMjEyMzIsIm5iZiI6MTczOTIyMDkzMiwicGF0aCI6Ii8zMDkzMjAxMi8zNTY4ODg1MzEtZDg0NjZmNzEtODYxMC00ZGQ3LTg2ZDktYTg5ODkxMmY0YjJkLmpwZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAyMTAlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMjEwVDIwNTUzMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWI4MTM3YzVmMDMxMTQyOWI0ZTkyMzUwOTExOWNjYjI4NjA5YWFlOTUwNTIxZjA3N2E0ODg3M2YwY2IzNjdhYTcmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.EdLUWJ751W53yhU1_93UFS4mTIfpSj1yGEI_t3IPA3o)
You can use components for fields and options. But i don't recommend using RichText for options in the sidebar because of its floating panel.
Example of options:
public function options(): array
{
$builder = new GutengoodBuilder();
$builder
->addText('title', [
'label' => __('Block title', 'sage'),
])
->addSelect('title_tag', [
'label' => __('Title tag', 'sage'),
'choices' => [
[
'label' => 'h1',
'value' => 'h1',
],
[
'label' => 'h2',
'value' => 'h2',
],
],
'value' => 'h2', // default value
]);
return $builder->build();
}
Also possible to add the same components in the repeater:
![](https://private-user-images.githubusercontent.com/30932012/356889457-a2c60247-3c3d-47c4-bbc3-d8f36b4d626a.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkyMjEyMzIsIm5iZiI6MTczOTIyMDkzMiwicGF0aCI6Ii8zMDkzMjAxMi8zNTY4ODk0NTctYTJjNjAyNDctM2MzZC00N2M0LWJiYzMtZDhmMzZiNGQ2MjZhLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAyMTAlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMjEwVDIwNTUzMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTEwNGFmOWEyOWM0NmY5ZjdkYjgxZGI1ZjhjZjE3MDk4MGIyOWM1NDE0M2I5ZDQyMDcwYjc2ZWU0Nzk0MGFkMjMmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.ynUcXAIbTrpLaLpGyxQjVOY48LCC_Q4yqcuQFaUbejo)
![](https://private-user-images.githubusercontent.com/30932012/377821797-348b267c-423d-4672-904f-9e8948ca9a0f.jpg?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkyMjEyMzIsIm5iZiI6MTczOTIyMDkzMiwicGF0aCI6Ii8zMDkzMjAxMi8zNzc4MjE3OTctMzQ4YjI2N2MtNDIzZC00NjcyLTkwNGYtOWU4OTQ4Y2E5YTBmLmpwZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAyMTAlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMjEwVDIwNTUzMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTdjZTJmNTc5MWUyMGZiMWIzMjFhYWQyZWJmNGZmZGYwZjQzN2ZiN2Y4MTA2ZTIzOThjNDAwYmE5YTBhYmYxNjYmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.XVErXfJadZ5cZiQlHe7lapq3L3g-_cVCNl2y5A5CoPY)
$builder
->addSection('Basic Options', [
'open' => true,
])
->addRange('width', [
'label' => __('Block width', 'sage'),
'value' => 900,
])
->endSection()
->addSection('Colors')
->addColorPalette('bg_color', [
'label' => __('BG Color', 'sage'),
'colors' => [
[
'name' => 'black',
'color' => '#000',
'slug' => 'black',
],
],
])
->endSection();
Curently work only with Select and Toggle. Example:
$builder
->addToggle('is_video')
->addText('video_id')
->conditional('is_video', true); // video_id field will be displayed if the video toggle checkbox is checked
Pass your data (fields and options) to the block view
public function getBlockData(array $attributes, string $content): array
{
$data = [
'items' => array_filter(array_map(fn(array $item): ?array => !empty($item['title']) ? $item : null, (array) ($attributes['items'] ?? []))),
'width' => (int) ($attributes['width'] ?? 900),
];
return [...parent::getBlockData($attributes, $content), ...$data];
}
Front-end block assets
public function getAssets(): array
{
return [
[
'handle' => 'gallery',
// optional: conditional logic
'condition' => fn(array $block): bool => !empty($block['attrs']['is_slider']) || !empty($block['attrs']['is_lightbox']),
],
];
}
If you need additional external dependencies:
public function getAssets(): array
{
return [
[
'handle' => 'payment-form',
'dependencies' => ['cloudpayments-widget'], // before register this script in your theme
'condition' => fn(array $block): bool => true === is_user_logged_in(), // optional
],
];
}
Just set meta true for the component, save the block in the editor and check postmeta
->addToggle('is_hide_images', [
'label' => __('Hide images?', 'sage'),
'meta' => true,
])
![](https://private-user-images.githubusercontent.com/30932012/399708008-966b1b39-1350-4236-86f2-4b61979449ef.jpg?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkyMjEyMzIsIm5iZiI6MTczOTIyMDkzMiwicGF0aCI6Ii8zMDkzMjAxMi8zOTk3MDgwMDgtOTY2YjFiMzktMTM1MC00MjM2LTg2ZjItNGI2MTk3OTQ0OWVmLmpwZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAyMTAlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMjEwVDIwNTUzMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTQ1NTgwM2I3ODEwZjBmOTdiMDZlMmM0MWRiMGY4MjgzNDkzNDUzYzRkYWYxZGI3ZTNlZDA2ZDNmODdmNTY5YzAmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.q2nytgyR8k0xDs3xAzSmPRASZhJ5_h4l_9ymzu3YbVE)
You don't need block js for register for the editor. But if needed you can set $editor_script like this
public bool $editor_script = true;
Then add your custom jsx here resources/scripts/editor/blocks
Example https://github.com/yarovikov/gutengood-examples/blob/main/app/Editor/Panels/PageOptions.php
Gutengood works by default only with content. But you can pass sidebar content using content_with_gutengood_blocks
filter:
/**
* Pass blocks content from the widget area to gutengood absract block for checking and enqueue if isset
* Based on https://github.com/WordPress/gutenberg/issues/44616
*/
add_filter('content_with_gutengood_blocks', function (string $content): string {
/**
* Use page template, meta, etc to check sidebar presence on the page
* This is important to avoid loading block assets on the page when you have added blocks in the widget area even when the sidebar is not on the page
* Example with is_hide_sidebar meta:
*/
if (!is_page() || true === (bool) get_post_meta(get_the_ID(), 'is_hide_sidebar', true)) {
return $content;
}
$widgets = wp_get_sidebars_widgets();
if (empty($widgets)) {
return $content;
}
global $wp_widget_factory;
$blocks_content = [];
foreach ($widgets as $key => $value) {
if ('sidebar' === $key) { // sidebar key required
if (isset($value) && is_array($value) && !empty($value)) {
foreach ($value as $widget_id) {
$parsed_id = wp_parse_widget_id($widget_id);
$widget_object = $wp_widget_factory->get_widget_object($parsed_id['id_base']);
if ($widget_object && isset($parsed_id['number'])) {
$all_instances = $widget_object->get_settings();
if (!empty($all_instances)) {
$instance = $all_instances[$parsed_id['number']];
$serialized_instance = serialize($instance);
$prepared['instance']['encoded'] = base64_encode($serialized_instance);
$prepared['instance']['hash'] = wp_hash($serialized_instance);
if (!empty($widget_object->widget_options['show_instance_in_rest'])) {
$prepared['instance']['raw'] = empty($instance) ? new stdClass : $instance;
$blocks_content[] = $prepared['instance']['raw']['content'];
}
}
}
}
}
}
}
if (empty($blocks_content)) {
return $content;
}
return $content . implode('', $blocks_content);
});
Feel free to add your own examples 👻