Back to skills
SkillHub ClubShip Full StackFull StackTesting

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.

Stars
923
Hot score
99
Updated
March 20, 2026
Overall rating
C4.0
Composite score
4.0
Best-practice grade
B78.7

Install command

npx @skill-hub/cli install wordpress-agent-skills-wp-block-development

Repository

WordPress/agent-skills

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 repository

Best 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

Claude CodeCodex CLIGemini CLIOpenCode

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.


```

wp-block-development | SkillHub