From superpowers-sage
Builds native Gutenberg blocks in WordPress Sage projects without ACF: block.json, edit.js/save.js, InnerBlocks, transforms, PHP render_callback. Use for nesting, variations, JS-heavy editors beyond ACF Composer.
npx claudepluginhub codigodoleo/superpowers-sage --plugin superpowers-sageThis skill uses the workspace's default tool permissions.
When building editor blocks that require capabilities beyond what ACF Composer provides: InnerBlocks nesting, block transforms, block variations, JS-heavy editor UI, or block deprecations. Also use when you need fine-grained control over the editor experience or when a block's primary complexity is in its editor behavior rather than its field configuration.
Develops WordPress Gutenberg blocks: block.json metadata/attributes, dynamic rendering (render.php), deprecations/migrations, view scripts, and @wordpress/scripts/@wordpress/create-block workflows.
Scaffolds new ACF Composer Gutenberg blocks in Sage themes with custom element architecture, scoped CSS, theme variations, block.json, Blade views, and JS lifecycle.
Generates or edits WordPress Gutenberg blocks with Greenshift/GreenLight plugin. Converts HTML/CSS/JS designs or data/charts to paste-ready blocks for Gutenberg editor, or reverses to vanilla code.
Share bugs, ideas, or general feedback.
When building editor blocks that require capabilities beyond what ACF Composer provides: InnerBlocks nesting, block transforms, block variations, JS-heavy editor UI, or block deprecations. Also use when you need fine-grained control over the editor experience or when a block's primary complexity is in its editor behavior rather than its field configuration.
| Requirement | ACF Composer | Native Block |
|---|---|---|
| Complex field groups (repeaters, flexible content) | Best choice | Avoid |
| InnerBlocks (nested block areas) | Not supported | Required |
| Block transforms (convert between block types) | Not supported | Required |
| Block variations (same block, different presets) | Limited | Full support |
| JS-heavy editor controls | Limited | Full support |
| Block deprecations (safe markup changes) | Not applicable | Required |
| Quick data-entry blocks | Best choice | Overkill |
Both can coexist in the same project. ACF blocks and native blocks appear side by side in the inserter.
resources/
scripts/
editor/
blocks/
hero/
index.js # Block registration (edit + save)
edit.js # Editor component
save.js # Save component (or null for dynamic)
style.css # Front-end styles
editor.css # Editor-only styles
block.json # Block metadata
See references/block-json-native.md for full field reference and attribute source types.
Use apiVersion 3 for WordPress 6.3+. Key fields: name, attributes, supports, editorScript, render.
See references/edit-save.md for complete edit/save examples, deprecations, and ServerSideRender.
edit — React component; interactive; output not storedsave — pure function; stored HTML; must match on re-parseSee references/dynamic-blocks.md for PHP render templates, Blade integration, and InnerBlocks with dynamic rendering.
For blocks that query WordPress data, set save: () => null and use the render field in block.json:
// render.php — receives $attributes, $content, $block
<div <?php echo get_block_wrapper_attributes(['class' => 'hero']); ?>>
<?php echo $content; ?>
</div>
See references/vite-integration.md for Vite configuration, @wordpress/* externals, and the single-bundle vs per-block entry pattern.
Key: declare @wordpress/* packages as externals in Vite — do not bundle them. Enqueue via enqueue_block_editor_assets:
add_action('enqueue_block_editor_assets', function () {
wp_enqueue_script(
'sage-editor-blocks',
Vite::asset('resources/scripts/editor/index.js'),
['wp-blocks', 'wp-element', 'wp-block-editor', 'wp-components'],
null,
true
);
});
import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor';
export default function Edit() {
const blockProps = useBlockProps({ className: 'card-grid' });
const innerBlocksProps = useInnerBlocksProps(blockProps, {
allowedBlocks: ['sage/card', 'core/paragraph'],
template: [['sage/card', {}], ['sage/card', {}]],
templateLock: false,
});
return <div {...innerBlocksProps} />;
}
| Symptom | Cause | Fix |
|---|---|---|
| "This block contains unexpected or invalid content" | Save function output does not match stored HTML | Fix save function or add a deprecation |
| Block not appearing in inserter | block.json not found or editor script not enqueued | Check file paths and script registration |
| Styles not applying | Missing get_block_wrapper_attributes() or useBlockProps() | Block supports require these wrappers |
| InnerBlocks empty on front end | $content variable not echoed in render template | Add echo $content to PHP render |
| Editor script errors | Missing WordPress script dependencies | Ensure wp-blocks, wp-element, wp-block-editor are listed as dependencies |
save function exactly reproduces the old output. Use browser console to compare expected vs actual HTML.register_block_type() silently fails, enable WP_DEBUG and check for errors. Common cause: invalid block.json syntax or missing required fields.