wp-block-development
Use when developing WordPress (Gutenberg) blocks: block.json metadata, register_block_type(_from_metadata), attributes/serialization, supports, dynamic rendering (render.php/render_callback), deprecations/migrations, viewScript vs viewScriptModule, and @wordpress/scripts/@wordpress/create-block build and test workflows.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install wordpress-agent-skills-wp-block-development
Repository
Skill path: skills/wp-block-development
Use when developing WordPress (Gutenberg) blocks: block.json metadata, register_block_type(_from_metadata), attributes/serialization, supports, dynamic rendering (render.php/render_callback), deprecations/migrations, viewScript vs viewScriptModule, and @wordpress/scripts/@wordpress/create-block build and test workflows.
Open repositoryBest for
Primary workflow: Ship Full Stack.
Technical facets: Full Stack, Testing.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: WordPress.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install wp-block-development into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/WordPress/agent-skills before adding wp-block-development to shared team environments
- Use wp-block-development for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: wp-block-development
description: "Use when developing WordPress (Gutenberg) blocks: block.json metadata, register_block_type(_from_metadata), attributes/serialization, supports, dynamic rendering (render.php/render_callback), deprecations/migrations, viewScript vs viewScriptModule, and @wordpress/scripts/@wordpress/create-block build and test workflows."
compatibility: "Targets WordPress 6.9+ (PHP 7.2.24+). Filesystem-based agent with bash + node. Some workflows require WP-CLI."
---
# WP Block Development
## When to use
Use this skill for block work such as:
- creating a new block, or updating an existing one
- changing `block.json` (scripts/styles/supports/attributes/render/viewScriptModule)
- fixing “block invalid / not saving / attributes not persisting”
- adding dynamic rendering (`render.php` / `render_callback`)
- block deprecations and migrations (`deprecated` versions)
- build tooling for blocks (`@wordpress/scripts`, `@wordpress/create-block`, `wp-env`)
## Inputs required
- Repo root and target (plugin vs theme vs full site).
- The block name/namespace and where it lives (path to `block.json` if known).
- Target WordPress version range (especially if using modules / `viewScriptModule`).
## Procedure
### 0) Triage and locate blocks
1. Run triage:
- `node skills/wp-project-triage/scripts/detect_wp_project.mjs`
2. List blocks (deterministic scan):
- `node skills/wp-block-development/scripts/list_blocks.mjs`
3. Identify the block root (directory containing `block.json`) you’re changing.
If this repo is a full site (`wp-content/` present), be explicit about *which* plugin/theme contains the block.
### 1) Create a new block (if needed)
If you are creating a new block, prefer scaffolding rather than hand-rolling structure:
- Use `@wordpress/create-block` to scaffold a modern block/plugin setup.
- If you need Interactivity API from day 1, use the interactive template.
Read:
- `references/creating-new-blocks.md`
After scaffolding:
1. Re-run the block list script and confirm the new block root.
2. Continue with the remaining steps (model choice, metadata, registration, serialization).
### 2) Ensure apiVersion 3 (WordPress 6.9+)
WordPress 6.9 enforces `apiVersion: 3` in the block.json schema. Blocks with apiVersion 2 or lower trigger console warnings when `SCRIPT_DEBUG` is enabled.
**Why this matters:**
- WordPress 7.0 will run the post editor in an iframe regardless of block apiVersion.
- apiVersion 3 ensures your block works correctly inside the iframed editor (style isolation, viewport units, media queries).
**Migration:** Changing from version 2 to 3 is usually as simple as updating the `apiVersion` field in `block.json`. However:
- Test in a local environment with the iframe editor enabled.
- Ensure any style handles are included in `block.json` (styles missing from the iframe won't apply).
- Third-party scripts attached to a specific `window` may have scoping issues.
Read:
- `references/block-json.md` (apiVersion and schema details)
### 3) Pick the right block model
- **Static block** (markup saved into post content): implement `save()`; keep attributes serialization stable.
- **Dynamic block** (server-rendered): use `render` in `block.json` (or `render_callback` in PHP) and keep `save()` minimal or `null`.
- **Interactive frontend behavior**:
- Prefer `viewScriptModule` for modern module-based view scripts where supported.
- If you're working primarily on `data-wp-*` directives or stores, also use `wp-interactivity-api`.
### 4) Update `block.json` safely
Make changes in the block’s `block.json`, then confirm registration matches metadata.
For field-by-field guidance, read:
- `references/block-json.md`
Common pitfalls:
- changing `name` breaks compatibility (treat it as stable API)
- changing saved markup without adding `deprecated` causes “Invalid block”
- adding attributes without defining source/serialization correctly causes “attribute not saving”
### 5) Register the block (server-side preferred)
Prefer PHP registration using metadata, especially when:
- you need dynamic rendering
- you need translations (`wp_set_script_translations`)
- you need conditional asset loading
Read and apply:
- `references/registration.md`
### 6) Implement edit/save/render patterns
Follow wrapper attribute best practices:
- Editor: `useBlockProps()`
- Static save: `useBlockProps.save()`
- Dynamic render (PHP): `get_block_wrapper_attributes()`
Read:
- `references/supports-and-wrappers.md`
- `references/dynamic-rendering.md` (if dynamic)
### 7) Inner blocks (block composition)
If your block is a “container” that nests other blocks, treat Inner Blocks as a first-class feature:
- Use `useInnerBlocksProps()` to integrate inner blocks with wrapper props.
- Keep migrations in mind if you change inner markup.
Read:
- `references/inner-blocks.md`
### 8) Attributes and serialization
Before changing attributes:
- confirm where the attribute value lives (comment delimiter vs HTML vs context)
- avoid the deprecated `meta` attribute source
Read:
- `references/attributes-and-serialization.md`
### 9) Migrations and deprecations (avoid "Invalid block")
If you change saved markup or attributes:
1. Add a `deprecated` entry (newest → oldest).
2. Provide `save` for old versions and an optional `migrate` to normalize attributes.
Read:
- `references/deprecations.md`
### 10) Tooling and verification commands
Prefer whatever the repo already uses:
- `@wordpress/scripts` (common) → run existing npm scripts
- `wp-env` (common) → use for local WP + E2E
Read:
- `references/tooling-and-testing.md`
## Verification
- Block appears in inserter and inserts successfully.
- Saving + reloading does not create “Invalid block”.
- Frontend output matches expectations (static: saved markup; dynamic: server output).
- Assets load where expected (editor vs frontend).
- Run the repo’s lint/build/tests that triage recommends.
## Failure modes / debugging
If something fails, start here:
- `references/debugging.md` (common failures + fastest checks)
- `references/attributes-and-serialization.md` (attributes not saving)
- `references/deprecations.md` (invalid block after change)
## Escalation
If you’re uncertain about upstream behavior/version support, consult canonical docs first:
- WordPress Developer Resources (Block Editor Handbook, Theme Handbook, Plugin Handbook)
- Gutenberg repo docs for bleeding-edge behaviors
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### references/creating-new-blocks.md
```markdown
# Creating new blocks (scaffolding)
Use this file when you are creating a new block (or a new block plugin) from scratch.
## Preferred path: `@wordpress/create-block`
`@wordpress/create-block` scaffolds a modern block setup that tends to track current best practices.
Typical options to decide up front:
- TypeScript vs JavaScript
- Static vs dynamic (`render.php` / server rendering)
- Whether the block should be interactive on the frontend
Canonical docs:
- https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/
## “Most up-to-date” interactive blocks
For a modern interactive block, prefer the official Interactivity API template:
- Template: `@wordpress/create-block-interactive-template`
This template is designed to integrate:
- Interactivity API directives (`data-wp-*`)
- module-based view scripts (`viewScriptModule`)
- server rendering (`render.php`)
References:
- https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/
- https://make.wordpress.org/core/2024/03/04/a-first-look-at-the-interactivity-api-in-wordpress-6-5/
## Manual fallback (when scaffolding is not available)
If you cannot run `create-block` (no Node tooling or restricted network):
1. Create a plugin or theme location that will register the block.
2. Create a block folder with a valid `block.json`.
3. Register via `register_block_type_from_metadata()` in PHP.
4. Add editor JS and (optionally) frontend view assets.
Then follow the rest of `wp-block-development` for metadata, registration, and serialization.
```
### references/block-json.md
```markdown
# `block.json` (metadata) guidance
Use this file when you’re editing `block.json` fields or choosing between script/styles fields.
## Practical rules
- Treat `name` as stable API (renaming breaks existing content).
- Prefer adding new functionality without changing saved markup; if markup must change, add a `deprecated` version.
- Keep assets scoped: editor assets should not ship to frontend unless needed.
## API version + schema
**WordPress 6.9+ requires apiVersion 3.** The block.json schema now only validates blocks with `apiVersion: 3`. Older versions (1 or 2) trigger console warnings when `SCRIPT_DEBUG` is enabled.
**Why apiVersion 3 matters:**
- The post editor will be iframed if all registered blocks have apiVersion 3+.
- WordPress 7.0 will always use the iframe editor regardless of apiVersion.
- Benefits: style isolation (admin CSS won't affect editor content), correct viewport units (vw, vh), native media queries.
**Migration checklist:**
1. Update `apiVersion` to `3` in block.json.
2. Ensure all style handles are declared in block.json (styles not included won't load in the iframe).
3. Test blocks that rely on third-party scripts (window scoping may differ).
4. Add a `$schema` to improve editor tooling and validation.
References:
- Block metadata: https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/
- Block API versions: https://developer.wordpress.org/block-editor/reference-guides/block-api/block-api-versions/
- Iframe migration guide: https://developer.wordpress.org/block-editor/reference-guides/block-api/block-api-versions/block-migration-for-iframe-editor-compatibility/
- Block schema index: https://schemas.wp.org/
## Modern asset fields to know
This is not a full schema; it’s a “what matters in practice” list:
- `editorScript` / `editorStyle`: editor-only assets.
- `script` / `style`: shared assets.
- `viewScript` / `viewStyle`: frontend view assets.
- `viewScriptModule`: module-based frontend scripts (newer WP).
- `render`: points to a PHP render file for dynamic blocks (newer WP).
## Helpful upstream references
- Block metadata reference (block.json):
- https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/
- Block.json schema (editor tooling):
- https://schemas.wp.org/trunk/block.json
```
### references/registration.md
```markdown
# Registration patterns (PHP-first)
Use this file when you need to register blocks robustly across repo types (plugin/theme/site).
## Prefer metadata registration
Prefer:
- `register_block_type_from_metadata( $path_to_block_dir, $args = [] )`
Why:
- keeps metadata authoritative (`block.json`)
- supports dynamic render (`render`) and other metadata-driven fields
- enables cleaner asset handling
Upstream reference:
- https://developer.wordpress.org/reference/functions/register_block_type_from_metadata/
## Where to register
- Plugins: register on `init` in the main plugin bootstrap or a dedicated loader.
- Themes: register on `init` (or `after_setup_theme` if you need theme supports first), but keep it predictable.
## Dynamic render mapping
If `block.json` includes `render`, ensure the file exists relative to the block root.
Inside the render file, use `get_block_wrapper_attributes()` for wrapper attributes.
```
### references/supports-and-wrappers.md
```markdown
# Supports and wrapper attributes
Use this file when changing `supports` or when your block wrapper styling behaves unexpectedly.
## Required patterns
- In `edit()`, use `useBlockProps()`.
- In `save()`, use `useBlockProps.save()`.
If the block is dynamic (PHP render), use:
- `get_block_wrapper_attributes()`
Upstream reference:
- https://developer.wordpress.org/block-editor/reference-guides/block-api/block-supports/
- https://developer.wordpress.org/reference/functions/get_block_wrapper_attributes/
```
### references/dynamic-rendering.md
```markdown
# Dynamic blocks (server rendering)
Use this file when converting a block to dynamic, or debugging frontend output mismatch.
## Choose the mechanism
- Prefer `render` in `block.json` (dynamic render file).
- Alternative: pass `render_callback` when registering the block in PHP.
## Wrapper attributes
In PHP render output, always use:
- `get_block_wrapper_attributes()`
This preserves support-generated classes/styles.
## Practical checklist
- Ensure PHP file exists and is reachable from the block root.
- Ensure registration runs on every request (not only in admin).
- Keep `save()` empty or `null` for fully dynamic output, unless you intentionally save fallback markup.
```
### references/inner-blocks.md
```markdown
# Inner Blocks (nested blocks)
Use this file when your block contains other blocks (container blocks).
## Canonical references
- Nested blocks guide: https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/nested-blocks-inner-blocks/
- `@wordpress/block-editor` package: https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/
- Block supports: https://developer.wordpress.org/block-editor/reference-guides/block-api/block-supports/
## Practical patterns
- Editor:
- Use `useInnerBlocksProps( useBlockProps(), { ... } )` to combine wrapper props with inner blocks.
- Use templates/allowed blocks only when you have a clear UX reason (too strict is frustrating).
- Save:
- Use `useInnerBlocksProps.save( useBlockProps.save(), { ... } )` if you need wrapper props.
- Output nested content via `<InnerBlocks.Content />` when appropriate.
## Common pitfalls
- Only one `InnerBlocks` should exist per block.
- Changing the wrapper structure that contains inner blocks can invalidate existing content; consider deprecations/migrations.
- If you need to constrain allowed blocks, prefer doing it intentionally and documenting why.
```
### references/attributes-and-serialization.md
```markdown
# Attributes and serialization
Use this file when attributes aren’t saving, content becomes “Invalid block”, or you’re changing markup.
## How attributes persist
Attributes can come from:
- the comment delimiter JSON (common and stable)
- the block’s saved HTML (from tags/attributes)
- context
Read the canonical guide for supported `source`/`selector`/`attribute` patterns:
- https://developer.wordpress.org/block-editor/reference-guides/block-api/block-attributes/
## Common pitfalls
- Changing saved HTML without a `deprecated` version breaks existing posts.
- Using the `meta` attribute source (deprecated) causes long-term pain; avoid it.
- Choosing brittle selectors leads to attributes “not found” when markup changes slightly.
```
### references/deprecations.md
```markdown
# Deprecations and migrations
Use this file when you must change saved markup or attribute shapes without breaking existing content.
## `deprecated` basics
Block deprecations are handled in JS block registration.
- Add older implementations to `deprecated` (newest → oldest).
- Each deprecated entry can include:
- `attributes`
- `supports`
- `save`
- `migrate`
Upstream reference:
- https://developer.wordpress.org/block-editor/reference-guides/block-api/block-deprecation/
## Practical guardrails
- Keep fixtures: store example content for each deprecated version.
- When in doubt, add a migration path rather than silently changing selectors.
```
### references/tooling-and-testing.md
```markdown
# Tooling and testing
Use this file when deciding what commands to run and what “good verification” looks like.
## Common toolchains
- `@wordpress/scripts` for build/lint/test:
- https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/
- `@wordpress/create-block` to scaffold new blocks:
- https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/
- Interactivity API template for `create-block`:
- https://www.npmjs.com/package/@wordpress/create-block-interactive-template
- `@wordpress/env` (wp-env) for local WordPress environments:
- https://developer.wordpress.org/block-editor/reference-guides/packages/packages-env/
## Verification checklist
- `npm run build` (or repo equivalent) succeeds.
- JS lint passes (repo-specific).
- E2E tests pass if present.
- Manual: insert block, save post, reload editor, confirm no “Invalid block”.
```
### references/debugging.md
```markdown
# Debugging quick routes
## Block doesn’t appear in inserter
- Confirm `block.json` `name` is valid and the block is registered.
- Confirm build output exists and scripts are enqueued.
- If using PHP registration, confirm `register_block_type_from_metadata()` runs (wrong hook/file not loaded is common).
## “This block contains unexpected or invalid content”
- You changed saved markup or attribute parsing.
- Add `deprecated` versions and a migration path.
- Reproduce with an old post containing the previous markup.
## Attributes not saving
- Confirm attribute definition matches actual markup.
- If the value is in delimiter JSON, avoid brittle selectors.
- Avoid `meta` attribute source (deprecated).
## Console warnings about apiVersion (WordPress 6.9+)
If you see "The block 'namespace/block' is registered with API version 2 or lower":
- Update `apiVersion` to `3` in block.json.
- This warning only appears when `SCRIPT_DEBUG` is true.
- WordPress 7.0 will require apiVersion 3 for proper iframe editor support.
## Styles not applying in editor (apiVersion 3 / iframe)
If styles work on frontend but not in the editor:
- Ensure style handles are declared in block.json (`editorStyle`, `style`).
- Styles not included in block.json won't load inside the iframed editor.
- Check for Dashicons or other dependencies that need explicit inclusion.
```