Beginner's Guide to Gutenberg Block Development

What are Gutenberg Blocks?

Gutenberg is the modern editor for WordPress, using “blocks” as the fundamental unit of content. Each block is an independent content element:

  • Paragraphs, headings, lists
  • Images, videos, audio
  • Buttons, columns, groups
  • Custom blocks

Development Environment Setup

Required Tools

# Node.js 18+
node -v

# npm or yarn
npm -v

# WordPress Local Environment
# Recommended: Local by Flywheel, wp-env, Docker

Creating a Block Plugin

# Use the official scaffolding tool
npx @wordpress/create-block my-first-block

# Enter the directory
cd my-first-block

# Start development
npm start

Block Structure

File Organization

my-first-block/
├── build/              # Compiled output
├── src/
│   ├── block.json      # Block metadata
│   ├── edit.js         # Editor component
│   ├── save.js         # Frontend output
│   ├── index.js        # Entry file
│   ├── editor.scss     # Editor styles
│   └── style.scss      # Frontend styles
├── my-first-block.php  # Main plugin file
└── package.json

block.json

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "my-plugin/my-first-block",
  "version": "1.0.0",
  "title": "My First Block",
  "category": "widgets",
  "icon": "smiley",
  "description": "A simple block example",
  "supports": {
    "html": false
  },
  "textdomain": "my-first-block",
  "editorScript": "file:./index.js",
  "editorStyle": "file:./index.css",
  "style": "file:./style-index.css"
}

Writing Block Code

edit.js - Editor Component

import { useBlockProps, RichText } from "@wordpress/block-editor";
import { __ } from "@wordpress/i18n";

export default function Edit({ attributes, setAttributes }) {
  const blockProps = useBlockProps();
  
  return (
    <div {...blockProps}>
      <RichText
        tagName="p"
        value={attributes.content}
        onChange={(content) => setAttributes({ content })}
        placeholder={__("Enter text...", "my-first-block")}
      />
    </div>
  );
}

save.js - Frontend Output

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>
  );
}

index.js - Registering the Block

import { registerBlockType } from "@wordpress/blocks";
import Edit from "./edit";
import Save from "./save";
import metadata from "./block.json";

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

Block Attributes

Defining Attributes

// block.json
{
  "attributes": {
    "content": {
      "type": "string",
      "source": "html",
      "selector": "p"
    },
    "alignment": {
      "type": "string",
      "default": "left"
    },
    "backgroundColor": {
      "type": "string"
    }
  }
}

Using Attributes

// edit.js
const { content, alignment, backgroundColor } = attributes;

setAttributes({ alignment: "center" });

Common Components

Toolbar Controls

import { BlockControls, AlignmentToolbar } from "@wordpress/block-editor";

<BlockControls>
  <AlignmentToolbar
    value={alignment}
    onChange={(newAlign) => setAttributes({ alignment: newAlign })}
  />
</BlockControls>

Sidebar Settings

import { InspectorControls } from "@wordpress/block-editor";
import { PanelBody, ColorPicker } from "@wordpress/components";

<InspectorControls>
  <PanelBody title={__("Settings", "my-block")}>
    <ColorPicker
      color={backgroundColor}
      onChange={(color) => setAttributes({ backgroundColor: color })}
    />
  </PanelBody>
</InspectorControls>

Dynamic Blocks

Server-side rendered blocks:

// PHP registration
register_block_type(__DIR__ . "/build", [
    "render_callback" => "render_my_dynamic_block"
]);

function render_my_dynamic_block($attributes) {
    $recent_posts = wp_get_recent_posts([
        "numberposts" => 5,
        "post_status" => "publish"
    ]);
    
    $output = "<ul>";
    foreach ($recent_posts as $post) {
        $output .= "<li>" . esc_html($post["post_title"]) . "</li>";
    }
    $output .= "</ul>";
    
    return $output;
}
// save.js returns null
export default function Save() {
  return null;
}

Debugging Tips

Enabling Development Mode

// wp-config.php
define("SCRIPT_DEBUG", true);
define("WP_DEBUG", true);

Browser Tools

  • React DevTools
  • Redux DevTools (for viewing block state)

Common Errors

Error Cause Solution
Block validation failed save output doesn’t match stored markup Check save.js
Cannot read property property undefined Set default values
Invalid block block.json format error Validate JSON

Learning Resources


For block development issues, you can ask the @gutenberg expert for assistance!