- 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.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkxNzQzOTYsIm5iZiI6MTczOTE3NDA5NiwicGF0aCI6Ii8zMDkzMjAxMi8zOTk3MDYwMTctMWRjNWU4ZDMtOGY1OS00YTNiLWIzMzAtNDkzMGJlN2FjYzQzLmpwZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAyMTAlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMjEwVDA3NTQ1NlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWNhYWEyOWU1ZWVlOWQ1OWI2NGZhN2JlYWJkYjg5NjBiNDYxOWFmOGEyYTI3OGQ0YWU3N2NjM2NhMDI2YmU0ZTQmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.OVWnGX6xph2ZZG153c_aKbhcPLegtpWulmG66qcYxyw)
![](https://private-user-images.githubusercontent.com/30932012/356887690-db5d6c99-317f-4800-99c3-77617e71a4b0.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkxNzQzOTYsIm5iZiI6MTczOTE3NDA5NiwicGF0aCI6Ii8zMDkzMjAxMi8zNTY4ODc2OTAtZGI1ZDZjOTktMzE3Zi00ODAwLTk5YzMtNzc2MTdlNzFhNGIwLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAyMTAlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMjEwVDA3NTQ1NlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTU1YmFkZjE3ODFjODBiOTE1NWFhMGE1MWRmYzZjMmNlZDU0MjQ0NGJlYjNiMWNmZDA5ODQ5NDQ3ZmExMTcwMmYmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.AZ3gPwrbsRHq_2lw5pMPGjPmznX-H5OOXd6n7v2imQ0)
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.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkxNzQzOTYsIm5iZiI6MTczOTE3NDA5NiwicGF0aCI6Ii8zMDkzMjAxMi8zNTY4ODgxNzUtMzgxZjU0OWMtODlhYS00YmMwLWJjOGEtNmRjMDMxOWQwNDExLmpwZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAyMTAlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMjEwVDA3NTQ1NlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTZlNjE2ODk0ZGRkYzU2MDg4MTY3ZDkxN2U0M2Y5NDdkNTRiZWYxYTg0ZjI1NGRiYjE5ODRlMjAyNDZkYmE4ZjEmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.Us_q2oKec46k9npExiAMz_92zSAL05WzlDUeJ8RmBt4)
![](https://private-user-images.githubusercontent.com/30932012/356888531-d8466f71-8610-4dd7-86d9-a898912f4b2d.jpg?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkxNzQzOTYsIm5iZiI6MTczOTE3NDA5NiwicGF0aCI6Ii8zMDkzMjAxMi8zNTY4ODg1MzEtZDg0NjZmNzEtODYxMC00ZGQ3LTg2ZDktYTg5ODkxMmY0YjJkLmpwZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAyMTAlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMjEwVDA3NTQ1NlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTVjNzYzNGM3ZmQyNWVlY2JjZmE0OWZhOGRiNGM4Njk3ZDhmODZhZTIxZTZhNGM2MmUyMjgyM2YzZDEyY2ViOWYmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.PvqGXRpqr43Kd0hcL49Sidy4m7zQ_6-XZFwntu3G_2s)
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.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkxNzQzOTYsIm5iZiI6MTczOTE3NDA5NiwicGF0aCI6Ii8zMDkzMjAxMi8zNTY4ODk0NTctYTJjNjAyNDctM2MzZC00N2M0LWJiYzMtZDhmMzZiNGQ2MjZhLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAyMTAlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMjEwVDA3NTQ1NlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWRmYzMyMGUwNGM3YzJmMDdmMTdkMTVhZGNjNDA0NjY5MzQ4Y2MwNDVkZmZiNTMzNDczNTExOGNjNjRlNDM5OTkmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.-tadB08S7n91Rs0zjysZh1iyzEBkIJ1y-40VekWmfUE)
![](https://private-user-images.githubusercontent.com/30932012/377821797-348b267c-423d-4672-904f-9e8948ca9a0f.jpg?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkxNzQzOTYsIm5iZiI6MTczOTE3NDA5NiwicGF0aCI6Ii8zMDkzMjAxMi8zNzc4MjE3OTctMzQ4YjI2N2MtNDIzZC00NjcyLTkwNGYtOWU4OTQ4Y2E5YTBmLmpwZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAyMTAlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMjEwVDA3NTQ1NlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTcxNTk0NzcxZjljMTMxZGQyMzVlMmNmYmIwZTA5ZGIwMTMzZTUwOWRhODY2NDllNjhjMzA2OTU5MDI5MmQ0ZmImWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.Ao6cR9bRiB31j4h_wvAi1KdikR_NRcPh31LE30Xz4yc)
$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.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkxNzQzOTYsIm5iZiI6MTczOTE3NDA5NiwicGF0aCI6Ii8zMDkzMjAxMi8zOTk3MDgwMDgtOTY2YjFiMzktMTM1MC00MjM2LTg2ZjItNGI2MTk3OTQ0OWVmLmpwZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAyMTAlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMjEwVDA3NTQ1NlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTkwNWE4ZmY3MmZhYmYyOGI4Mjc4N2JiN2ZlYTVmZmZjN2MwMWIyZDI2MjNlZjdjZWFlNDNjNTY2YTVlOTNlMzQmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.qefWOEOhNepPLWqRGxR9jZynRu9XabxTgmOb7u2iFBg)
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 👻