August 10, 2025

How to Develop a Custom Gutenberg Block in WordPress (Coupon Example)

Gutenberg, the block editor introduced in WordPress 5.0, is much more than a content editor. It’s also a framework for building custom blocks that empower editors to create dynamic, reusable content without touching HTML.

In this guide, we’ll go step-by-step through building a “Coupon Claim” block, but the same process applies to any custom Gutenberg block you want to create.


Why Build a Custom Gutenberg Block?

By default, WordPress offers a set of core blocks—paragraphs, images, headings, etc. But in real projects, you often need custom components:

  • Product cards
  • Testimonials
  • Call-to-action buttons
  • Event listings
  • Coupons (our example)

Custom Gutenberg blocks:

  • Give non-technical editors an easy UI for custom content
  • Keep design consistent across pages
  • Can be static (HTML stored in post content) or dynamic (PHP rendering on the frontend)

Development Environment Setup

Before building a block, you need:

  • Local WordPress installation (XAMPP, MAMP, LocalWP, or Docker)
  • Node.js & npm installed
  • WordPress plugin development basics

We’ll create our block as a plugin so it’s portable.


Generate the Block Plugin

WordPress provides a tool to quickly scaffold a block:

npx @wordpress/create-block coupon-claim

This command:

  • Creates a plugin folder (coupon-claim/)
  • Sets up block.json with metadata
  • Adds default React code (src/edit.js, src/save.js)
  • Configures build tools (webpack, Babel)

Understanding block.json

block.json is the configuration file for your block.

Example:

{
  "apiVersion": 2,
  "name": "myplugin/coupon-claim",
  "title": "Coupon Claim",
  "category": "widgets",
  "icon": "tickets-alt",
  "description": "A block to display coupons with title, code, and a claim button.",
  "attributes": {
    "title": { "type": "string", "default": "" },
    "code": { "type": "string", "default": "" },
    "desc": { "type": "string", "default": "" },
    "buttonText": { "type": "string", "default": "Claim Now" },
    "buttonLink": { "type": "string", "default": "" }
  },
  "editorScript": "file:./build/index.js"
}

Building the Editor UI (edit.js)

Gutenberg blocks are written in React.
We’ll use InspectorControls to add settings in the editor sidebar.

import { __ } from '@wordpress/i18n';
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
import { PanelBody, TextControl, Button } from '@wordpress/components';

export default function Edit({ attributes, setAttributes }) {
    const { title, code, desc, buttonText, buttonLink } = attributes;

    return (
        <div {...useBlockProps({ className: 'coupon-card' })}>
            <InspectorControls>
                <PanelBody title={__('Coupon Settings', 'coupon-claim')}>
                    <TextControl
                        label="Coupon Title"
                        value={title}
                        onChange={(v) => setAttributes({ title: v })}
                    />
                    <TextControl
                        label="Coupon Code"
                        value={code}
                        onChange={(v) => setAttributes({ code: v })}
                    />
                    <TextControl
                        label="Description"
                        value={desc}
                        onChange={(v) => setAttributes({ desc: v })}
                    />
                    <TextControl
                        label="Button Text"
                        value={buttonText}
                        onChange={(v) => setAttributes({ buttonText: v })}
                    />
                    <TextControl
                        label="Button Link"
                        value={buttonLink}
                        onChange={(v) => setAttributes({ buttonLink: v })}
                    />
                </PanelBody>
            </InspectorControls>

            <div className="coupon-preview">
                <h3>{title || 'Coupon Title'}</h3>
                <p>{code ? `Coupon Code: ${code}` : 'Coupon Code: XXXX'}</p>
                <small>{desc || 'Coupon description here'}</small>
                <br />
                <Button isPrimary>{buttonText || 'Claim Now'}</Button>
            </div>
        </div>
    );
}


Rendering on the Frontend

You can make the block static (HTML saved in post content) or dynamic (PHP rendering).
For coupons that change over time, dynamic blocks are better.

Example render_callback in PHP:

function myplugin_render_coupon_block( $attributes ) {
    ob_start();
    ?>
    <div class="coupon-card">
        <h3><?php echo esc_html( $attributes['title'] ); ?></h3>
        <p>Coupon Code: <?php echo esc_html( $attributes['code'] ); ?></p>
        <small><?php echo esc_html( $attributes['desc'] ); ?></small>
        <br>
        <a href="<?php echo esc_url( $attributes['buttonLink'] ); ?>" class="btn">
            <?php echo esc_html( $attributes['buttonText'] ); ?>
        </a>
    </div>
    <?php
    return ob_get_clean();
}

Register the block in your main plugin file:

register_block_type(
    __DIR__,
    [ 'render_callback' => 'myplugin_render_coupon_block' ]
);

Build & Test

Run:

npm run build

This compiles your React code from src/ into build/, which WordPress uses.

Then:

  1. Activate the plugin in WordPress Admin → Plugins.
  2. Open the block editor.
  3. Search for “Coupon Claim” in the block inserter.
  4. Customize and insert into your post/page.

Next Steps

You can extend this coupon block by:

  • Adding an expiry date with a countdown timer.
  • Pulling live coupon data from WooCommerce.
  • Letting editors choose colors and styles.
  • Tracking coupon clicks for analytics.

In short:
Developing Gutenberg blocks follows a clear workflow:

  1. Scaffold the block (npx @wordpress/create-block).
  2. Configure block.json.
  3. Build the editor UI in React.
  4. Render content (static or dynamic).
  5. Compile and test.

Once you understand this pattern, you can build any type of custom content block for WordPress.