Skip to main content
When developing custom blocks for WordPress, it’s best practice to register them within plugins rather than themes. This guide details the file structure as produced by the create-block tool.
Registering blocks in plugins ensures they stay accessible even when users switch themes.

Plugin file structure

my-block-plugin/
├── plugin.php
├── package.json
├── src/
│   ├── block.json
│   ├── index.js
│   ├── edit.js
│   ├── save.js
│   ├── style.scss
│   ├── editor.scss
│   ├── render.php (optional)
│   └── view.js (optional)
└── build/
    ├── block.json
    ├── index.js
    ├── index.asset.php
    ├── style-index.css
    └── index.css

Main plugin file (plugin.php)

The main PHP file registers the block on the server using the register_block_type() function:
<?php
/**
 * Plugin Name: My Block Plugin
 * Description: A custom block plugin
 * Version: 1.0.0
 */

function my_block_plugin_register_block() {
    register_block_type( __DIR__ . '/build' );
}
add_action( 'init', 'my_block_plugin_register_block' );

Package configuration (package.json)

The package.json file configures the Node.js project, defining dependencies and build scripts:
{
  "name": "my-block-plugin",
  "version": "1.0.0",
  "scripts": {
    "build": "wp-scripts build",
    "start": "wp-scripts start"
  },
  "devDependencies": {
    "@wordpress/scripts": "^27.0.0"
  }
}

Source folder (src/)

The src folder contains raw, uncompiled code that gets processed during the build.

block.json

Defines the block’s metadata, streamlining registration across client and server:
{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "my-plugin/my-block",
  "title": "My Custom Block",
  "category": "widgets",
  "icon": "smiley",
  "editorScript": "file:./index.js",
  "editorStyle": "file:./index.css",
  "style": "file:./style-index.css"
}
Key properties in block.json:
  • editorScript: Path to bundled index.js (built from src/index.js)
  • style: Path to bundled style-index.css (built from src/style.scss)
  • editorStyle: Path to bundled index.css (built from src/editor.scss)
  • render: Path to render.php for dynamic blocks

index.js

The entry point for JavaScript loaded in the Block Editor:
import { registerBlockType } from '@wordpress/blocks';
import Edit from './edit';
import save from './save';
import metadata from './block.json';

registerBlockType( metadata.name, {
    edit: Edit,
    save,
} );

edit.js

The React component that renders the block’s editing interface:
import { useBlockProps } from '@wordpress/block-editor';

export default function Edit() {
    return (
        <p { ...useBlockProps() }>
            Hello World - Block Editor
        </p>
    );
}

save.js

Returns the static HTML markup saved to the database:
import { useBlockProps } from '@wordpress/block-editor';

export default function save() {
    return (
        <p { ...useBlockProps.save() }>
            Hello World - Frontend
        </p>
    );
}

style.scss

Styles loaded in both the Block Editor and front end:
.wp-block-my-plugin-my-block {
    padding: 20px;
    background-color: #f0f0f0;
}

editor.scss

Additional styles applied only in the Block Editor:
.wp-block-my-plugin-my-block {
    border: 2px dashed #ccc;
}

render.php (optional)

Defines server-side rendering for dynamic blocks:
<?php
$wrapper_attributes = get_block_wrapper_attributes();
?>
<div <?php echo $wrapper_attributes; ?>>
    <p><?php echo esc_html( $attributes['content'] ); ?></p>
</div>

view.js (optional)

JavaScript loaded on the front end when the block is displayed:
console.log( 'Block loaded on frontend' );

Build folder (build/)

The build folder contains compiled and optimized code generated by wp-scripts:
1

Source compilation

Modern JavaScript is transpiled to be compatible with wider browser support
2

Asset bundling

Files are minified and bundled for efficient loading
3

WordPress integration

WordPress enqueues files from the build folder
Always point register_block_type() to the build directory, not src.

Build process

Run these commands to build your block:
npm run build
For development with automatic rebuilding:
npm start

Next steps

Block metadata

Learn about block.json properties

Block registration

Understand how blocks are registered

Build docs developers (and LLMs) love