- 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:


Use Edit Button to see editable components in the block


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:


$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,
])

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 👻