Frontend Code Review
langflow-ai/langflow
Review frontend code (.tsx, .ts, .js files) for quality, performance, and correctness against Langflow's frontend conventions.
WordPress block editor code review and Gutenberg block development patterns for WordPress 6.x+.
$ npx skills add jorgerosal/wordpress-skills --skill wp-block-development -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install jorgerosal/wordpress-skills wp-block-development --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/jorgerosal/wordpress-skills.git skills-src && mkdir -p .claude/skills && cp -r skills-src/claude-skills/wp-block-development .claude/skills/wp-block-development && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "wp-block-development" agent skill from https://github.com/jorgerosal/wordpress-skills/tree/main/claude-skills/wp-block-development into .claude/skills/wp-block-development/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "wp-block-development", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/jorgerosal/wordpress-skills/tree/main/claude-skills/wp-block-developmentType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add jorgerosal/wordpress-skills --skill wp-block-development -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install jorgerosal/wordpress-skills wp-block-development --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/jorgerosal/wordpress-skills.git skills-src && mkdir -p .agents/skills && cp -r skills-src/claude-skills/wp-block-development .agents/skills/wp-block-development && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "wp-block-development" agent skill from https://github.com/jorgerosal/wordpress-skills/tree/main/claude-skills/wp-block-development into .agents/skills/wp-block-development/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "wp-block-development", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add jorgerosal/wordpress-skills --skill wp-block-development -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install jorgerosal/wordpress-skills wp-block-development --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/jorgerosal/wordpress-skills.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/claude-skills/wp-block-development .cursor/skills/wp-block-development && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "wp-block-development" agent skill from https://github.com/jorgerosal/wordpress-skills/tree/main/claude-skills/wp-block-development into .cursor/skills/wp-block-development/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "wp-block-development", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/jorgerosal/wordpress-skills.git --path claude-skills/wp-block-development--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add jorgerosal/wordpress-skills --skill wp-block-development -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install jorgerosal/wordpress-skills wp-block-development --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/jorgerosal/wordpress-skills.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/claude-skills/wp-block-development .gemini/skills/wp-block-development && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "wp-block-development" agent skill from https://github.com/jorgerosal/wordpress-skills/tree/main/claude-skills/wp-block-development into .gemini/skills/wp-block-development/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "wp-block-development", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install jorgerosal/wordpress-skills wp-block-developmentInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add jorgerosal/wordpress-skills --skill wp-block-development -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/jorgerosal/wordpress-skills.git skills-src && mkdir -p .github/skills && cp -r skills-src/claude-skills/wp-block-development .github/skills/wp-block-development && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "wp-block-development" agent skill from https://github.com/jorgerosal/wordpress-skills/tree/main/claude-skills/wp-block-development into .github/skills/wp-block-development/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "wp-block-development", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add jorgerosal/wordpress-skills --skill wp-block-development -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install jorgerosal/wordpress-skills wp-block-development --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/jorgerosal/wordpress-skills.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/claude-skills/wp-block-development .opencode/skills/wp-block-development && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "wp-block-development" agent skill from https://github.com/jorgerosal/wordpress-skills/tree/main/claude-skills/wp-block-development into .opencode/skills/wp-block-development/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "wp-block-development", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
wp-block-developmentWordPress block editor code review and Gutenberg block development patterns for WordPress 6.x+.
Wp Block Development is an agent skill from jorgerosal/wordpress-skills. WordPress block editor code review and Gutenberg block development patterns for WordPress 6.x+. Use when reviewing block code, auditing block.json schema, checking editor components, validating render callbacks, analyzing block attributes, verifying InnerBlocks usage, detecting block validation errors, reviewing Interactivity API directives, or when user mentions "block review", "Gutenberg", "block development", "block editor", "block.json", "useBlockProps", "InnerBlocks", "Interactivity API", "data-wp-bind"…
Its SKILL.md is about 12k tokens, which your agent loads only when the skill is triggered. The skill folder holds 5 other files, including reference files (for example `references/block-json-guide.md`, `references/dynamic-blocks-guide.md` and `references/editor-patterns.md`).
It sits in Frontend & Design, covering React components, Backend development and Code review. It works with WordPress, React and PHP. The repository describes itself as: ✅ 🎉 Claude skills and Codex skills for Wordpress development❗️. The licence is MIT.
7 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit 8c96442. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
rgFrom the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
schemas.wp.orgFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Wp Block Development loads about 12k tokens when it runs, and up to ~41k if it reads all its reference files. Until then it costs about 227 tokens; SKILL.md has 2,810 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from jorgerosal/wordpress-skills at commit 8c96442, republished under its MIT licence (© jorgerosal). 2,810 words, ~12,061 tokens.
.claude/skills/wp-block-development/SKILL.md (or your agent's skills folder). This skill also uses 4 other files; get the full folder from GitHub.Systematic block development review for WordPress 6.x+ block editor (Gutenberg). Core principle: WordPress blocks follow a dual-architecture pattern—React components for the editor (edit function) and either static HTML (save function) or server-side PHP (render_callback/render file) for the frontend. block.json is the single source of truth. Review validates block.json schema, editor patterns (React/JSX), server-side rendering (PHP), attribute handling, deprecation management, and Interactivity API usage. Report findings grouped by file (PHP and JS/JSX files intermixed by actual path) with line numbers, severity labels (CRITICAL/WARNING/INFO), and BAD/GOOD code pairs.
Note: This skill reviews BOTH PHP and JavaScript/React code. PHP follows WordPress PHP Coding Standards (spaces in parentheses, array() not [], Yoda conditions). JavaScript/JSX follows WordPress JS coding standards (tab indentation, JSDoc comments, camelCase for variables/functions, PascalCase for components).
Use when:
Don't use for:
Follow this seven-step workflow for systematic block reviews:
Identify block type and context
Validate block.json schema (BLK-02, BLK-06, BLK-08, BLK-10)
Check edit function (BLK-05, BLK-20)
Check save function or render callback
Scan for CRITICAL patterns
Check WARNING patterns
Note INFO improvements
Report using output format below. If security concerns found (unescaped render output, user input in Interactivity API state), add note: "Security issues detected. Run /wp-sec-review for comprehensive security analysis." If plugin architecture issues found (init hook registration, ABSPATH check missing), add note: "Plugin architecture issues detected. Run /wp-plugin-review for comprehensive plugin review."
Source vs Build Review: Review src/ files for code patterns (developer intent). Flag if build/ directory is missing or stale (check index.asset.php timestamp vs src/ modification times). Do NOT review build/ files for code quality - they are compiled output.
apiVersion field:
"apiVersion": 3name field:
"name": "my-plugin/my-block" (lowercase, dashes, letters, numbers)attributes field:
supports field:
editorScript/script/viewScript/viewScriptModule fields:
"file:./index.js" relative to block.json locationrender field:
"file:./render.php" relative to block.json location$schema field:
"$schema": "https://schemas.wp.org/trunk/block.json"useBlockProps usage:
const blockProps = useBlockProps(); then <div { ...blockProps }>Import patterns:
import { useBlockProps } from '@wordpress/block-editor'; BAD: const { useBlockProps } = window.wp.blockEditor;InspectorControls and BlockControls:
RichText usage:
<RichText tagName="p" value={ attributes.content } onChange={ ( content ) => setAttributes( { content } ) } />InnerBlocks usage:
<InnerBlocks allowedBlocks={ [ 'core/paragraph' ] } template={ [ [ 'core/heading' ] ] } />useSelect/useDispatch performance:
Internationalization:
__( 'Text', 'text-domain' ) for all user-facing stringsStatic blocks (save returns JSX):
const blockProps = useBlockProps.save(); then <div { ...blockProps }>Dynamic blocks (save returns null or InnerBlocks.Content):
<InnerBlocks.Content /> even for dynamic blocksDeterministic save:
ABSPATH check:
get_block_wrapper_attributes():
$wrapper_attributes = get_block_wrapper_attributes(); then <div <?php echo $wrapper_attributes; ?>>Output escaping:
Function parameters:
register_block_type():
add_action( 'init', 'callback' ); then register_block_type( __DIR__ . '/build/block-name' );Multi-block registration:
Plugin header and ABSPATH:
deprecated array:
migrate function:
migrate( attributes ) { return { ...attributes, newField: 'default' }; }Ordering:
WP 6.5+ version marker:
Requires at least: 6.5 in plugin headerrender.php directives:
view.js (frontend store):
import { store } from '@wordpress/interactivity'; then store( 'namespace', { actions, state } );Key distinction:
Security crossover:
providesContext (parent block):
"providesContext": { "myPlugin/keyName": "attributeName" }usesContext (child block):
"usesContext": [ "myPlugin/keyName" ]function Edit( { context } ) { const value = context['myPlugin/keyName']; }Block transforms (BLK-13 partial):
Block variations (BLK-13):
Block styles:
Block patterns (BLK-18):
Block hooks (BLK-19):
Block Bindings API (BLK-17):
@wordpress/scripts build config:
Template lock:
<InnerBlocks templateLock="all" /> prevents block addition/removal/reorderingUse these rg commands for quick block scanning. Organized by severity. Cover BOTH PHP and JS/JSX files.
# block.json without apiVersion 3
rg -l --iglob 'block.json' . | xargs -I{} sh -c "rg -q '\"apiVersion\"\\s*:\\s*3' '{}' || echo '{}'"
# JS/JSX files with window.wp.* instead of @wordpress/* imports
rg -n "window\.wp\." . -g '*.{js,jsx}'
# save function without useBlockProps.save() in JSX files (manual candidate list)
rg -n "function save|const save\s*=|save:\s*\(" . -g '*.{js,jsx}'
# render callback using wp_kses_post on $content parameter (breaks embeds)
rg -n "wp_kses_post\s*\(\s*\$content\s*\)" . -g '*.php'
# Dynamic block with InnerBlocks but save returns null
# Manual check: if edit.js uses InnerBlocks, save.js should include InnerBlocks.Content.
rg -n "InnerBlocks" . -g '*.{js,jsx}'
rg -n "return\s+null" . -g '*.{js,jsx}'
# Attribute with source: 'meta' (deprecated)
rg -n "\"source\"\s*:\s*\"meta\"" . -g 'block.json'
# Missing register_block_type() call in plugin bootstrap files
rg -n "register_block_type\s*\(" . -g '*.php'# block.json missing supports field
rg -l --iglob 'block.json' . | xargs -I{} sh -c "rg -q '\"supports\"' '{}' || echo '{}'"
# edit function without useBlockProps() in JS/JSX files (manual candidate list)
rg -n "function Edit|export default function Edit|const Edit\s*=" . -g '*.{js,jsx}'
# Hardcoded strings not wrapped in __() in JS/JSX files
rg -n "title\s*:\s*['\"][A-Z]" . -g '*.{js,jsx}'
# render.php without get_block_wrapper_attributes()
rg -l --iglob 'render.php' . | xargs -I{} sh -c "rg -q 'get_block_wrapper_attributes' '{}' || echo '{}'"
# PHP render files without defined( 'ABSPATH' ) check
rg -l --iglob 'render.php' . | xargs -I{} sh -c "rg -q 'defined.*ABSPATH' '{}' || echo '{}'"
# save function with side effects (Math.random, Date.now)
rg -n "Math\.random|Date\.now|new Date\(" . -g 'save.js'
# Missing block.json $schema field
rg -l --iglob 'block.json' . | xargs -I{} sh -c "rg -q '\"\\$schema\"' '{}' || echo '{}'"
# useSelect with multiple separate calls (performance anti-pattern)
# Manual check: inspect files with repeated useSelect() calls.
rg -n "useSelect\s*\(" . -g '*.{js,jsx}'# Using render_callback in PHP instead of render file in block.json
grep -rn "render_callback" --include="*.php" .
# Missing viewScript/viewScriptModule in block.json (no frontend JS)
grep -L "viewScript\|viewScriptModule" --include="block.json" .
# apiVersion 2 (suggest upgrade to 3)
grep -rn "\"apiVersion\": 2" --include="block.json" .
# Missing keywords in block.json
grep -L "\"keywords\"" --include="block.json" .
# Not using block hooks for auto-insertion opportunities
# (Manual check: Review block purpose for auto-insertion potential)Note: Grep patterns for JavaScript require different regex than PHP patterns. Use --include="*.js" --include="*.jsx" for JS files, --include="*.php" for PHP files.
Context-aware review notes based on block distribution and loading context:
Structure: One block.json at root or src/, single build output Review adjustments: Standard review, no special considerations Pattern: Plugin folder contains single src/ directory with index.js, edit.js, save.js
Structure: Multiple blocks in src/block-one/, src/block-two/, each with block.json Review adjustments:
Structure: Published as npm package or WordPress.org plugin with multiple blocks Review adjustments:
Structure: Registered in functions.php, block files in /blocks/ or /inc/blocks/ Review adjustments:
Common block patterns organized by concern. All examples use WordPress coding standards (PHP: spaces in parentheses, array() not [], Yoda conditions; JS: tab indentation, camelCase, PascalCase for components).
Complete schema with all fields annotated (apiVersion 3):
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "my-plugin/my-block",
"title": "My Block",
"category": "widgets",
"icon": "smiley",
"description": "A custom block example",
"keywords": [ "custom", "example" ],
"version": "1.0.0",
"textdomain": "my-plugin",
"attributes": {
"content": {
"type": "string",
"source": "html",
"selector": "p",
"default": ""
},
"showImage": {
"type": "boolean",
"default": false
}
},
"supports": {
"html": false,
"color": {
"background": true,
"text": true
},
"spacing": {
"margin": true,
"padding": true
},
"typography": {
"fontSize": true,
"lineHeight": true
},
"align": [ "wide", "full" ],
"anchor": true
},
"editorScript": "file:./index.js",
"editorStyle": "file:./index.css",
"style": "file:./style-index.css",
"viewScriptModule": "file:./view.js"
}PHP + JS registration pattern:
❌ BAD: Missing ABSPATH check, not hooked to init
<?php
function my_plugin_register_block() {
register_block_type( __DIR__ . '/build/my-block' );
}
my_plugin_register_block(); // Runs immediately, not on init✅ GOOD: Complete registration with ABSPATH and init hook
<?php
defined( 'ABSPATH' ) || exit;
function my_plugin_register_blocks() {
register_block_type( __DIR__ . '/build/my-block' );
}
add_action( 'init', 'my_plugin_register_blocks' );✅ GOOD: Multi-block registration
<?php
defined( 'ABSPATH' ) || exit;
function my_plugin_register_blocks() {
register_block_type( __DIR__ . '/build/block-one' );
register_block_type( __DIR__ . '/build/block-two' );
register_block_type( __DIR__ . '/build/block-three' );
}
add_action( 'init', 'my_plugin_register_blocks' );Edit with useBlockProps, save with useBlockProps.save, RichText + RichText.Content:
❌ BAD: Missing useBlockProps in edit and save
// edit.js
export default function Edit( { attributes, setAttributes } ) {
return (
<div className="my-block">
<input
value={ attributes.content }
onChange={ ( e ) => setAttributes( { content: e.target.value } ) }
/>
</div>
);
}
// save.js
export default function save( { attributes } ) {
return (
<div className="my-block">
<p>{ attributes.content }</p>
</div>
);
}✅ GOOD: Complete static block with useBlockProps and RichText
// edit.js
import { useBlockProps, RichText } from '@wordpress/block-editor';
export default function Edit( { attributes, setAttributes } ) {
const blockProps = useBlockProps();
return (
<div { ...blockProps }>
<RichText
tagName="p"
value={ attributes.content }
onChange={ ( content ) => setAttributes( { content } ) }
placeholder="Enter content..."
/>
</div>
);
}
// save.js
import { useBlockProps, RichText } from '@wordpress/block-editor';
export default function save( { attributes } ) {
const blockProps = useBlockProps.save();
return (
<div { ...blockProps }>
<RichText.Content tagName="p" value={ attributes.content } />
</div>
);
}Edit with useBlockProps, save returns null (or InnerBlocks.Content), render.php with get_block_wrapper_attributes and escaping:
❌ BAD: render callback without wrapper attributes, unescaped output
<?php
function my_plugin_render_callback( $attributes ) {
return '<div><p>' . $attributes['content'] . '</p></div>';
}✅ GOOD: Complete dynamic block with proper escaping
<?php
// render.php
defined( 'ABSPATH' ) || exit;
$wrapper_attributes = get_block_wrapper_attributes();
?>
<div <?php echo $wrapper_attributes; ?>>
<p><?php echo esc_html( $attributes['content'] ); ?></p>
</div>// save.js - Dynamic blocks return null
export default function save() {
return null;
}✅ GOOD: Dynamic block registered with render file
<?php
// plugin.php
defined( 'ABSPATH' ) || exit;
function my_plugin_register_blocks() {
register_block_type( __DIR__ . '/build/my-block' );
// render file specified in block.json "render": "file:./render.php"
}
add_action( 'init', 'my_plugin_register_blocks' );allowedBlocks, template, templateLock, InnerBlocks.Content in save (even for dynamic blocks):
❌ BAD: Dynamic block with InnerBlocks but save returns null
// edit.js
import { useBlockProps, InnerBlocks } from '@wordpress/block-editor';
export default function Edit() {
const blockProps = useBlockProps();
return (
<div { ...blockProps }>
<InnerBlocks />
</div>
);
}
// save.js - CRITICAL: Nested blocks will be lost!
export default function save() {
return null;
}✅ GOOD: Dynamic block with InnerBlocks saves nested content
// edit.js
import { useBlockProps, InnerBlocks } from '@wordpress/block-editor';
export default function Edit() {
const blockProps = useBlockProps();
const ALLOWED_BLOCKS = [ 'core/paragraph', 'core/image', 'core/heading' ];
const TEMPLATE = [
[ 'core/heading', { placeholder: 'Card Title' } ],
[ 'core/paragraph', { placeholder: 'Card content...' } ],
];
return (
<div { ...blockProps }>
<InnerBlocks
allowedBlocks={ ALLOWED_BLOCKS }
template={ TEMPLATE }
templateLock={ false }
/>
</div>
);
}
// save.js - MUST save InnerBlocks.Content even for dynamic blocks
import { InnerBlocks } from '@wordpress/block-editor';
export default function save() {
return <InnerBlocks.Content />;
}✅ GOOD: render.php uses $content parameter for InnerBlocks HTML
<?php
// render.php
defined( 'ABSPATH' ) || exit;
$wrapper_attributes = get_block_wrapper_attributes();
?>
<div <?php echo $wrapper_attributes; ?>>
<h3><?php echo esc_html( $attributes['title'] ); ?></h3>
<?php echo $content; // InnerBlocks HTML - DO NOT use wp_kses_post() ?>
</div>edit (useBlockProps()) and save (useBlockProps.save()), merging custom props:
❌ BAD: Missing useBlockProps
export default function Edit() {
return <div className="my-block">Content</div>;
}
function save() {
return <div className="my-block">Content</div>;
}✅ GOOD: useBlockProps with custom props merged
import { useBlockProps } from '@wordpress/block-editor';
export default function Edit( { attributes } ) {
const blockProps = useBlockProps( {
className: 'my-custom-class',
style: { backgroundColor: attributes.bgColor },
} );
return <div { ...blockProps }>Content</div>;
}
function save( { attributes } ) {
const blockProps = useBlockProps.save( {
className: 'my-custom-class',
style: { backgroundColor: attributes.bgColor },
} );
return <div { ...blockProps }>Content</div>;
}type/source/selector/default patterns, attribute type matching:
❌ BAD: Type mismatch, no default, deprecated source
{
"attributes": {
"count": {
"type": "number",
"source": "text",
"selector": ".count"
},
"metaValue": {
"type": "string",
"source": "meta",
"meta": "my_meta_key"
}
}
}✅ GOOD: Correct type/source matching, defaults, modern patterns
{
"attributes": {
"content": {
"type": "string",
"source": "html",
"selector": "p",
"default": ""
},
"count": {
"type": "number",
"source": "attribute",
"selector": ".count",
"attribute": "data-count",
"default": 0
},
"url": {
"type": "string",
"source": "attribute",
"selector": "a",
"attribute": "href",
"default": ""
},
"items": {
"type": "array",
"source": "query",
"selector": "li",
"query": {
"text": {
"type": "string",
"source": "text"
}
},
"default": []
}
}
}✅ GOOD: Meta attribute using useEntityProp (not deprecated source)
import { useEntityProp } from '@wordpress/core-data';
export default function Edit() {
const [ meta, setMeta ] = useEntityProp( 'postType', 'post', 'meta' );
const value = meta.my_meta_key;
const updateMeta = ( newValue ) => {
setMeta( { ...meta, my_meta_key: newValue } );
};
// Use value and updateMeta in component
}InspectorControls for sidebar, BlockControls for toolbar, PanelBody, ToggleControl, SelectControl:
❌ BAD: Settings hardcoded in block markup, no sidebar controls
export default function Edit( { attributes, setAttributes } ) {
return (
<div>
<label>
Show Image:
<input
type="checkbox"
checked={ attributes.showImage }
onChange={ ( e ) => setAttributes( { showImage: e.target.checked } ) }
/>
</label>
</div>
);
}✅ GOOD: InspectorControls for sidebar, BlockControls for toolbar
import { useBlockProps, InspectorControls, BlockControls, AlignmentToolbar } from '@wordpress/block-editor';
import { PanelBody, ToggleControl, SelectControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
export default function Edit( { attributes, setAttributes } ) {
const blockProps = useBlockProps();
return (
<>
<BlockControls>
<AlignmentToolbar
value={ attributes.align }
onChange={ ( align ) => setAttributes( { align } ) }
/>
</BlockControls>
<InspectorControls>
<PanelBody title={ __( 'Settings', 'my-plugin' ) }>
<ToggleControl
label={ __( 'Show featured image', 'my-plugin' ) }
checked={ attributes.showImage }
onChange={ ( showImage ) => setAttributes( { showImage } ) }
/>
<SelectControl
label={ __( 'Display style', 'my-plugin' ) }
value={ attributes.style }
options={ [
{ label: 'Default', value: 'default' },
{ label: 'Card', value: 'card' },
{ label: 'List', value: 'list' },
] }
onChange={ ( style ) => setAttributes( { style } ) }
/>
</PanelBody>
</InspectorControls>
<div { ...blockProps }>
{/* Block content */}
</div>
</>
);
}deprecated array, save + attributes + migrate, apiVersion in entries, newest first:
❌ BAD: Save function changed without deprecation
// Changed save markup - existing blocks will show validation error
function save( { attributes } ) {
const blockProps = useBlockProps.save();
return <div { ...blockProps }>{ attributes.content }</div>;
}✅ GOOD: Deprecation entry for old save function
import { useBlockProps } from '@wordpress/block-editor';
const deprecated = [
{
// Version 2: Added useBlockProps (newest deprecation first)
apiVersion: 2,
attributes: {
content: { type: 'string' },
},
save( { attributes } ) {
return <div className="my-block">{ attributes.content }</div>;
},
},
{
// Version 1: Original block
apiVersion: 2,
attributes: {
content: { type: 'string' },
},
save( { attributes } ) {
return <p>{ attributes.content }</p>;
},
migrate( attributes ) {
// Transform old attributes to new schema
return {
...attributes,
newField: 'default',
};
},
},
];
// Current version
function save( { attributes } ) {
const blockProps = useBlockProps.save();
return <div { ...blockProps }>{ attributes.content }</div>;
}
export default {
edit: Edit,
save,
deprecated,
};data-wp-interactive, data-wp-bind, data-wp-on, store(), wp_interactivity_state(), frontend-only emphasis:
❌ BAD: Custom JavaScript for frontend interaction
<?php
// render.php
?>
<div class="my-block">
<button id="toggle-button">Toggle</button>
<div id="content" style="display: none;">Hidden content</div>
</div>
<script>
document.getElementById('toggle-button').addEventListener('click', function() {
var content = document.getElementById('content');
content.style.display = content.style.display === 'none' ? 'block' : 'none';
});
</script>✅ GOOD: Interactivity API with directives (WP 6.5+)
<?php
// render.php
defined( 'ABSPATH' ) || exit;
wp_interactivity_state( 'myPlugin', array(
'isOpen' => false,
) );
$wrapper_attributes = get_block_wrapper_attributes();
?>
<div
<?php echo $wrapper_attributes; ?>
data-wp-interactive="myPlugin"
data-wp-context='{ "id": "<?php echo esc_attr( uniqid() ); ?>" }'
>
<button data-wp-on--click="actions.toggle">
<?php esc_html_e( 'Toggle', 'my-plugin' ); ?>
</button>
<div data-wp-bind--hidden="!state.isOpen">
<p><?php esc_html_e( 'Hidden content', 'my-plugin' ); ?></p>
</div>
</div>// view.js - Frontend store (NOT editor code)
import { store } from '@wordpress/interactivity';
store( 'myPlugin', {
state: {
isOpen: false,
},
actions: {
toggle: ( { state } ) => {
state.isOpen = ! state.isOpen;
},
},
} );block.json configuration:
{
"viewScriptModule": "file:./view.js"
}Note: Interactivity API is for FRONTEND only. Editor still uses React (edit.js).
@wordpress/* package imports vs window.wp.* legacy access:
❌ BAD: window.wp. global access (legacy)*
const { registerBlockType } = window.wp.blocks;
const { useBlockProps } = window.wp.blockEditor;
const { __ } = window.wp.i18n;✅ GOOD: @wordpress/ package imports (modern)*
import { registerBlockType } from '@wordpress/blocks';
import { useBlockProps } from '@wordpress/block-editor';
import { __ } from '@wordpress/i18n';color, spacing, typography, align, anchor in block.json:
❌ BAD: Missing supports field
{
"name": "my-plugin/my-block",
"title": "My Block"
}✅ GOOD: Complete supports configuration
{
"name": "my-plugin/my-block",
"title": "My Block",
"supports": {
"html": false,
"color": {
"background": true,
"text": true,
"gradients": true,
"link": true
},
"spacing": {
"margin": true,
"padding": true,
"blockGap": true
},
"typography": {
"fontSize": true,
"lineHeight": true,
"fontFamily": true,
"fontWeight": true
},
"align": [ "wide", "full" ],
"anchor": true
}
}variation objects with attributes and innerBlocks:
import { registerBlockVariation } from '@wordpress/blocks';
registerBlockVariation( 'core/columns', {
name: 'three-columns-equal',
title: 'Three Columns (Equal)',
description: 'Three columns with equal width',
icon: 'columns',
isDefault: false,
scope: [ 'block' ],
attributes: {
columns: 3,
},
innerBlocks: [
[ 'core/column' ],
[ 'core/column' ],
[ 'core/column' ],
],
} );from/to patterns:
import { createBlock } from '@wordpress/blocks';
export default {
edit: Edit,
save,
transforms: {
from: [
{
type: 'block',
blocks: [ 'core/paragraph' ],
transform: ( { content } ) => {
return createBlock( 'my-plugin/my-block', {
content,
} );
},
},
],
to: [
{
type: 'block',
blocks: [ 'core/paragraph' ],
transform: ( { content } ) => {
return createBlock( 'core/paragraph', {
content,
} );
},
},
],
},
};providesContext/usesContext:
// Parent block.json
{
"providesContext": {
"myPlugin/userId": "userId",
"myPlugin/userName": "userName"
},
"attributes": {
"userId": { "type": "number" },
"userName": { "type": "string" }
}
}// Child block.json
{
"usesContext": [ "myPlugin/userId", "myPlugin/userName" ]
}// Child edit.js
export default function Edit( { context } ) {
const userId = context['myPlugin/userId'];
const userName = context['myPlugin/userName'];
return (
<div>
<p>User: { userName } ({ userId })</p>
</div>
);
}blockHooks with dynamic blocks only:
❌ BAD: Block hooks with static block
{
"blockHooks": {
"core/post-content": "after"
}
}// save.js - Static save = block hooks won't work
function save() {
return <div>Content</div>;
}✅ GOOD: Block hooks with dynamic block
{
"blockHooks": {
"core/post-content": "after"
},
"render": "file:./render.php"
}// save.js - Dynamic block (null save) = block hooks work
function save() {
return null;
}package.json scripts configuration:
❌ BAD: Missing build scripts
{
"scripts": {
"test": "echo \"No build toolchain configured\""
}
}✅ GOOD: Complete @wordpress/scripts configuration
{
"scripts": {
"build": "wp-scripts build",
"start": "wp-scripts start",
"lint:js": "wp-scripts lint-js",
"format": "wp-scripts format",
"packages-update": "wp-scripts packages-update"
},
"devDependencies": {
"@wordpress/scripts": "^27.0.0"
}
}| Severity | Definition | Examples |
|---|---|---|
| CRITICAL | Block won't render OR crashes editor OR causes block validation error | Missing apiVersion or using apiVersion 1/2 without migration path, invalid attribute type causing save validation error, save function mismatch without deprecation entry, missing useBlockProps.save() in apiVersion 3 block, InnerBlocks in edit but save returns null (not InnerBlocks.Content), wp_kses_post() on InnerBlocks $content parameter, window.wp.* access breaking modern build, missing register_block_type() call, deprecated source:'meta' attribute pattern |
| WARNING | Block works but has quality/compatibility issues OR non-standard patterns | Deprecated API usage (apiVersion 1/2 without critical issues), missing block supports (color, spacing, typography), no deprecation handler for changed save function, hardcoded strings without i18n in JS/JSX or PHP, multiple separate useSelect calls (performance issue), missing get_block_wrapper_attributes() in render.php, missing defined( 'ABSPATH' ) in PHP files, save function with side effects (Math.random, Date.now) |
| INFO | Best practice improvements OR optimization opportunities | Could use render PHP file instead of render_callback function, missing viewScript/viewScriptModule for frontend interactions, not using block.json $schema field, apiVersion 2 (upgrade to 3 available), missing keywords in block.json, block hooks opportunity for auto-insertion, could add block variations for common presets, missing template lock on fixed InnerBlocks layouts |
Report findings grouped by FILE (PHP and JS/JSX files intermixed by actual file path), with line numbers and severity labels. Use BAD/GOOD code pairs for each finding.
# WordPress Block Review: my-block-plugin
## FILE: src/index.js
### Line 3: WARNING - Legacy global access
window.wp.* access is legacy pattern. Use @wordpress/* package imports for modern build toolchain.
❌ **BAD:**
```javascript
const { registerBlockType } = window.wp.blocks;✅ GOOD:
import { registerBlockType } from '@wordpress/blocks';useBlockProps() not called in edit function. Block won't render correctly in editor.
❌ BAD:
export default function Edit() {
return <div className="my-block">Content</div>;
}✅ GOOD:
import { useBlockProps } from '@wordpress/block-editor';
export default function Edit() {
const blockProps = useBlockProps();
return <div { ...blockProps }>Content</div>;
}apiVersion 3 blocks require useBlockProps.save(). Block validation errors will occur.
❌ BAD:
function save() {
return <div className="my-block">Content</div>;
}✅ GOOD:
import { useBlockProps } from '@wordpress/block-editor';
function save() {
const blockProps = useBlockProps.save();
return <div { ...blockProps }>Content</div>;
}Using wp_kses_post() on $content breaks embed blocks and oEmbed processing. InnerBlocks content is already sanitized.
❌ BAD:
<?php echo wp_kses_post( $content ); ?>✅ GOOD:
<?php echo $content; // InnerBlocks already sanitized ?>Add ABSPATH check at top of file to prevent direct access.
❌ BAD:
<?php
$wrapper_attributes = get_block_wrapper_attributes();✅ GOOD:
<?php
defined( 'ABSPATH' ) || exit;
$wrapper_attributes = get_block_wrapper_attributes();apiVersion 2 still works but upgrade to 3 recommended for iframe editor isolation (WP 6.3+).
❌ BAD:
{
"apiVersion": 2
}✅ GOOD:
{
"apiVersion": 3
}Add $schema field for IDE validation support.
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
...
}Total issues: 7
Block validation risk: HIGH - Missing useBlockProps.save() will cause validation errors
Security note: Missing ABSPATH check detected. Run /wp-sec-review for comprehensive security analysis.
Plugin note: Block registration detected. Run /wp-plugin-review for plugin architecture review.
## Common Mistakes (BLK-25)
Patterns that look like issues but are NOT problems:
| Pattern | Why It's NOT a Problem | Context |
|---------|------------------------|---------|
| **Dynamic block save() returning null** | Server-side PHP renders output. save() doesn't need to return markup. | Correct for render_callback or render file usage |
| **InnerBlocks.Content without useBlockProps wrapper** | Valid for dynamic blocks that only need to persist nested blocks. | Dynamic blocks can return bare InnerBlocks.Content in save() |
| **viewScript not needed** | Block has no frontend JavaScript behavior. | Editor-only blocks or static HTML blocks don't need viewScript |
| **render_callback without return** | Valid if callback echoes output (though return is preferred). | WordPress handles both echo and return patterns |
| **Missing style/editorStyle in block.json** | Block uses theme defaults or has no custom styling. | Not all blocks need custom styles |
| **apiVersion 2 blocks** | Still work in WordPress 6.x+, just missing iframe isolation features. | Upgrade recommended but not critical |
| **Block without supports** | Valid for simple blocks that don't need color/spacing/typography. | Intentional for minimal blocks |
| **save() function with className in wrapper** | apiVersion 2 auto-injects classes. apiVersion 3 requires useBlockProps. | apiVersion-dependent behavior, not a bug |
| **window.wp.* in browser console** | Expected for debugging. Only @wordpress/* imports in source code matter. | Console access fine, source imports should use packages |
| **Interactivity API not used** | Blocks with no frontend interaction don't need Interactivity API. | Editor-only or static blocks work without frontend JS |
| **No block.json deprecation for attribute additions** | Adding new optional attributes doesn't break existing blocks. | Only save() markup changes need deprecation |
| **InnerBlocks without template** | Free-form nesting is valid. Template is optional. | Intentional when users should choose any blocks |
## Version Compatibility Reference
Quick reference for WordPress version requirements:
| Feature | WordPress Version | Notes |
|---------|-------------------|-------|
| block.json metadata support | 5.8+ | Single source of truth for block registration |
| apiVersion 2 | 5.6+ | Automatic wrapper class injection in save() |
| apiVersion 3 | 6.3+ | Iframe editor isolation, manual wrapper handling required |
| InspectorControls group prop | 6.2+ | Settings/Appearance tabs in sidebar |
| Block Hooks API | 6.4+ | Auto-insertion, dynamic blocks only |
| Interactivity API | 6.5+ | data-wp-* directives, viewScriptModule |
| viewScriptModule field | 6.5+ | ES module support for Interactivity API |
| Block Bindings API | 6.5+ | Dynamic attribute connections |
| useEntityProp hook | 5.9+ | Replacement for deprecated source:'meta' |
## Deep-Dive References
For advanced block development patterns, load these companion reference documents:
| Task | Reference to Load |
|------|-------------------|
| block.json schema validation, field-by-field reference, attribute type/source/selector patterns | `references/block-json-guide.md` |
| React/JSX editor component patterns, useBlockProps, RichText, InspectorControls, BlockControls, useSelect/useDispatch | `references/editor-patterns.md` |
| Server-side rendering, render_callback vs render file, get_block_wrapper_attributes, output escaping, $attributes/$content/$block parameters | `references/dynamic-blocks-guide.md` |
| Interactivity API directives (data-wp-*), wp_interactivity_state(), store() patterns, frontend state management | `references/interactivity-api-guide.md` |
**Note:** Reference docs provide deep-dive content. This SKILL.md is self-sufficient for standard block reviews.
**Security crossover:** When encountering security-relevant patterns (unescaped render output, user input in Interactivity API state, missing ABSPATH checks), this skill provides brief reminders but defers to wp-security-review for comprehensive security analysis. For detailed security patterns, use `/wp-sec-review` command.
**Plugin crossover:** When encountering plugin-level patterns (register_block_type() on init hook, ABSPATH checks, plugin headers), this skill provides brief mentions but defers to wp-plugin-development for plugin architecture depth. For detailed plugin architecture review, use `/wp-plugin-review` command.© jorgerosal, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 4 other files (references) in claude-skills/wp-block-development of jorgerosal/wordpress-skills.
Open the folder on GitHubat commit 8c96442
Wp Block Development next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Wp Block Development this skilljorgerosal/wordpress-skills | 100 | — | ~12k | Automated safety check: Pass | MIT | |
| Frontend Code Reviewlangflow-ai/langflow | 156k | — | ~1.6k | Automated safety check: Pass | MIT | |
| Vercel React Best Practicesgambitph/Stackable | 350 | — | ~2k | Automated safety check: Pass | MIT | |
| React Code Reviewgiuseppe-trisciuoglio/developer-kit | 355 | — | ~2.6k | Automated safety check: Notes | MIT | |
| @pierre/diffs Code Renderingpierrecomputer/pierre | 6.2k | 2 repos | ~803 | Automated safety check: Pass | Apache-2.0 | |
| Tidyfactor StylerTidyFactor/Styler | 118 | — | ~1.7k | Automated safety check: Pass | Apache-2.0 |
langflow-ai/langflow
Review frontend code (.tsx, .ts, .js files) for quality, performance, and correctness against Langflow's frontend conventions.
gambitph/Stackable
React performance optimization guidelines from Vercel Engineering.
giuseppe-trisciuoglio/developer-kit
Provides comprehensive code review capability for React applications, validates component architecture, hooks usage, React 19 patterns, state management, performance optimization, accessibility…
pierrecomputer/pierre
Guides an agent through using @pierre/diffs to render syntax-highlighted files and diffs, and to build editing and review surfaces in React or plain JavaScript.
TidyFactor/Styler
Production framework styler and surgical RTL UI polish engine with Contextual Decision Layer (CDL).
ProgrammerAnthony/Anything-Extract
指导在项目中对前端代码(Next.js/React/TypeScript/Tailwind 等)进行结构、可维护性、性能与一致性审查,基于既定规则清单。适用于用户请求审查 .tsx/.ts/.js/.jsx 等前端文件或前端目录下的页面与组件。不用于后端代码(如 .py)。
jorgerosal/wordpress-skills
WordPress accessibility review for themes, blocks, plugins, and admin interfaces.
jorgerosal/wordpress-skills
WordPress ACF and content modeling review. An agent skill from jorgerosal/wordpress-skills.
jorgerosal/wordpress-skills
WordPress admin UI review and development guidance. An agent skill from jorgerosal/wordpress-skills.
jorgerosal/wordpress-skills
WordPress CI/CD and release engineering review guidance. An agent skill from jorgerosal/wordpress-skills.
jorgerosal/wordpress-skills
Headless WordPress and WPGraphQL review guidance. An agent skill from jorgerosal/wordpress-skills.
jorgerosal/wordpress-skills
WordPress migration and upgrade review. An agent skill from jorgerosal/wordpress-skills.
Categories
WordPress block editor code review and Gutenberg block development patterns for WordPress 6.x+. Wp Block Development is an agent skill from jorgerosal/wordpress-skills.x+.
Wp Block Development fits situations like: reviewing block code; auditing block.json schema; checking editor components; validating render callbacks.
Run `npx skills add jorgerosal/wordpress-skills --skill wp-block-development -a claude-code`. Or copy the skill folder (claude-skills/wp-block-development in jorgerosal/wordpress-skills) into .claude/skills/wp-block-development in your project. Claude Code loads it when a task matches its description.
Run `npx skills add jorgerosal/wordpress-skills --skill wp-block-development -a codex`. Or copy the skill folder (claude-skills/wp-block-development in jorgerosal/wordpress-skills) into .agents/skills/wp-block-development in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add jorgerosal/wordpress-skills --skill wp-block-development -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/wp-block-development, .gemini/skills/wp-block-development, .github/skills/wp-block-development and .opencode/skills/wp-block-development in your project.
Going by SKILL.md and its folder, Wp Block Development needs the command-line tools its instructions call (rg).
SKILL.md names 1 domain. In commands or code: schemas.wp.org; the agent is likely to contact it when it follows the instructions. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Wp Block Development is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 12k tokens (SKILL.md is roughly 48k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 28k tokens, read only when the agent opens those files.
Skills that share tags, products or a category with Wp Block Development: Frontend Code Review (langflow-ai/langflow, 156k stars), Vercel React Best Practices (gambitph/Stackable, 350 stars), React Code Review (giuseppe-trisciuoglio/developer-kit, 355 stars) and @pierre/diffs Code Rendering (pierrecomputer/pierre, 6.2k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
jorgerosal (a GitHub user) maintains it in jorgerosal/wordpress-skills, which has 100 GitHub stars. The repository holds 35 skills in this directory. The repository was last updated on June 7, 2026.
Source: jorgerosal/wordpress-skills on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.