• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 12+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » Step-by-Step Guide to building a custom dynamic lead collector block for Gutenberg using REST API custom routes

Step-by-Step Guide to building a custom dynamic lead collector block for Gutenberg using REST API custom routes

I. Plugin Setup and REST API Endpoint Registration

We’ll begin by establishing a basic WordPress plugin structure and registering a custom REST API route to handle lead submissions. This route will be responsible for receiving data from our Gutenberg block and processing it.

Create a new directory for your plugin, e.g., custom-lead-collector, within your WordPress installation’s wp-content/plugins/ directory. Inside this directory, create a main plugin file, e.g., custom-lead-collector.php.

Plugin Header

Add the standard WordPress plugin header to custom-lead-collector.php:

<?php
/**
 * Plugin Name: Custom Lead Collector
 * Description: A custom Gutenberg block for collecting leads via the REST API.
 * Version: 1.0
 * Author: Your Name
 * Author URI: https://yourwebsite.com
 * License: GPL2
 */

// Prevent direct access to the file.
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}
?>

Registering the REST API Route

We’ll use the rest_api_init action hook to register our custom endpoint. This endpoint will accept POST requests containing lead data.

/**
 * Register custom REST API route for lead submission.
 */
add_action( 'rest_api_init', function () {
    register_rest_route( 'custom-lead-collector/v1', '/submit', array(
        'methods'  => 'POST',
        'callback' => 'handle_lead_submission',
        'permission_callback' => '__return_true', // For simplicity, allow all users. Consider more robust permission checks for production.
    ) );
} );

/**
 * Callback function to handle lead submission.
 *
 * @param WP_REST_Request $request Full data about the request.
 * @return WP_REST_Response Response object.
 */
function handle_lead_submission( WP_REST_Request $request ) {
    $name  = sanitize_text_field( $request->get_param( 'name' ) );
    $email = sanitize_email( $request->get_param( 'email' ) );
    $message = sanitize_textarea_field( $request->get_param( 'message' ) ); // Optional field

    if ( empty( $name ) || empty( $email ) || !is_email( $email ) ) {
        return new WP_REST_Response( array( 'message' => 'Invalid input. Name and a valid email are required.' ), 400 );
    }

    // In a real-world scenario, you would:
    // 1. Sanitize and validate all incoming data rigorously.
    // 2. Store the lead data in the database (e.g., custom table, post meta).
    // 3. Send an email notification to the site administrator.
    // 4. Potentially integrate with a CRM or email marketing service.

    // For this example, we'll just simulate a successful submission.
    $response_data = array(
        'message' => 'Lead submitted successfully!',
        'data'    => array(
            'name'  => $name,
            'email' => $email,
        ),
    );

    return new WP_REST_Response( $response_data, 200 );
}

In handle_lead_submission, we retrieve parameters, perform basic sanitization and validation. For production, expand this with more robust checks and actual data persistence logic.

II. Gutenberg Block Development: Frontend and Editor Components

Next, we’ll develop the Gutenberg block. This involves creating JavaScript files for both the editor interface and the frontend rendering of the block. We’ll also enqueue these scripts.

Block Registration and Script Enqueueing

Create a new directory for your block’s assets, e.g., custom-lead-collector/src/. Inside this, create index.js for the block’s main registration and edit.js and save.js for the editor and frontend components, respectively. Also, create a block.json file.

In custom-lead-collector.php, add the following code to register the block and enqueue its scripts:

/**
 * Register the Gutenberg block.
 */
function register_custom_lead_collector_block() {
    // Register the block using block.json
    register_block_type( __DIR__ . '/build' );
}
add_action( 'init', 'register_custom_lead_collector_block' );

You’ll need a build process (e.g., Webpack) to compile your JavaScript. For simplicity, we’ll assume you have a build directory containing the compiled index.js. The block.json file will define the block’s metadata.

block.json

{
    "apiVersion": 2,
    "name": "custom-lead-collector/lead-form",
    "version": "0.1.0",
    "title": "Custom Lead Form",
    "category": "widgets",
    "icon": "email",
    "description": "A custom block to collect leads via the REST API.",
    "attributes": {
        "titleText": {
            "type": "string",
            "default": "Get in Touch"
        },
        "buttonText": {
            "type": "string",
            "default": "Send Message"
        }
    },
    "editorScript": "file:./build/index.js",
    "editorStyle": "file:./build/index.css",
    "style": "file:./build/style-index.css"
}

src/index.js (Block Registration)

This file registers the block and imports the editor and frontend components.

import { registerBlockType } from '@wordpress/blocks';
import './style.scss'; // Styles for both editor and frontend
import Edit from './edit';
import save from './save';

registerBlockType( 'custom-lead-collector/lead-form', {
    /**
     * @see ./edit.js
     */
    edit: Edit,
    /**
     * @see ./save.js
     */
    save,
} );

src/edit.js (Editor Component)

This component defines how the block appears and functions within the Gutenberg editor. It includes input fields for the title and button text, and a preview of the form.

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

export default function Edit( { attributes, setAttributes } ) {
    const blockProps = useBlockProps();
    const { titleText, buttonText } = attributes;

    const onChangeTitle = ( newTitle ) => {
        setAttributes( { titleText: newTitle } );
    };

    const onChangeButtonText = ( newButtonText ) => {
        setAttributes( { buttonText: newButtonText } );
    };

    return (
        <div { ...blockProps }>
            <RichText
                tagName="h2"
                value={ titleText }
                onChange={ onChangeTitle }
                placeholder={ __( 'Enter block title...', 'custom-lead-collector' ) }
                allowedFormats={ [ 'core/bold', 'core/italic' ] }
            />
            <div className="lead-form-fields">
                <input type="text" placeholder="Your Name" disabled />
                <input type="email" placeholder="Your Email" disabled />
                <textarea placeholder="Your Message" disabled></textarea>
            </div>
            <button disabled>{ buttonText }</button>
        </div>
    );
}

src/save.js (Frontend Component)

This component defines the static HTML that will be saved to the database for the frontend. Crucially, it will include a placeholder for the JavaScript that will handle the actual form submission.

import { useBlockProps, RichText } from '@wordpress/block-editor';

export default function save( { attributes } ) {
    const blockProps = useBlockProps.save();
    const { titleText, buttonText } = attributes;

    return (
        <div { ...blockProps }>
            <RichText.Content tagName="h2" value={ titleText } />
            <div className="lead-form-fields">
                <input type="text" name="lead_name" placeholder="Your Name" required />
                <input type="email" name="lead_email" placeholder="Your Email" required />
                <textarea name="lead_message" placeholder="Your Message"></textarea>
            </div>
            <button type="button" className="submit-lead-button">{ buttonText }</button>
            <div className="lead-form-response"></div>
        </div>
    );
}

III. Frontend JavaScript for REST API Interaction

The final piece is the JavaScript that runs on the frontend to handle user input, submit data to our REST API endpoint, and display feedback to the user. This script will be enqueued and associated with our block.

src/frontend.js (or integrated into index.js build)

Create a new file, e.g., src/frontend.js. This script will be responsible for the dynamic behavior.

document.addEventListener( 'DOMContentLoaded', function() {
    // Find all instances of our lead form block
    const leadFormBlocks = document.querySelectorAll( '.wp-block-custom-lead-collector-lead-form' );

    leadFormBlocks.forEach( block => {
        const submitButton = block.querySelector( '.submit-lead-button' );
        const responseDiv = block.querySelector( '.lead-form-response' );
        const nameInput = block.querySelector( 'input[name="lead_name"]' );
        const emailInput = block.querySelector( 'input[name="lead_email"]' );
        const messageTextarea = block.querySelector( 'textarea[name="lead_message"]' );

        if ( submitButton ) {
            submitButton.addEventListener( 'click', async () => {
                const name = nameInput.value.trim();
                const email = emailInput.value.trim();
                const message = messageTextarea.value.trim();

                // Basic client-side validation
                if ( ! name || ! email || ! /\S+@\S+\.\S+/.test( email ) ) {
                    responseDiv.textContent = 'Please enter a valid name and email address.';
                    responseDiv.style.color = 'red';
                    return;
                }

                responseDiv.textContent = 'Submitting...';
                responseDiv.style.color = 'grey';

                try {
                    const response = await fetch( '/wp-json/custom-lead-collector/v1/submit', {
                        method: 'POST',
                        headers: {
                            'Content-Type': 'application/json',
                            'X-WP-Nonce': block.dataset.wpNonce || '' // Important for authenticated requests if needed
                        },
                        body: JSON.stringify( {
                            name: name,
                            email: email,
                            message: message,
                        } ),
                    } );

                    const result = await response.json();

                    if ( response.ok ) {
                        responseDiv.textContent = result.message || 'Success!';
                        responseDiv.style.color = 'green';
                        // Clear form fields on success
                        nameInput.value = '';
                        emailInput.value = '';
                        messageTextarea.value = '';
                    } else {
                        responseDiv.textContent = result.message || 'An error occurred. Please try again.';
                        responseDiv.style.color = 'red';
                    }
                } catch ( error ) {
                    console.error( 'Submission error:', error );
                    responseDiv.textContent = 'Network error. Please try again.';
                    responseDiv.style.color = 'red';
                }
            } );
        }
    } );
} );

Enqueueing the Frontend Script

You need to enqueue this script so it loads on the frontend. A common approach is to hook into wp_enqueue_scripts and check if the block is present on the page. For simplicity, we’ll enqueue it globally, but for performance, consider conditional loading.

/**
 * Enqueue frontend script for the lead collector block.
 */
function enqueue_lead_collector_frontend_script() {
    // Assuming your build process outputs frontend.js in a 'build' directory.
    // If frontend logic is part of index.js, adjust accordingly.
    wp_enqueue_script(
        'custom-lead-collector-frontend',
        plugin_dir_url( __FILE__ ) . 'build/frontend.js', // Path to your compiled frontend script
        array( 'wp-element', 'wp-api-fetch' ), // Dependencies: wp-element for React, wp-api-fetch for REST API calls
        filemtime( plugin_dir_path( __FILE__ ) . 'build/frontend.js' ),
        true // Load in footer
    );

    // If you need to pass PHP data (like nonce) to JS, use wp_localize_script
    // wp_localize_script( 'custom-lead-collector-frontend', 'customLeadCollectorData', array(
    //     'restUrl' => rest_url( 'custom-lead-collector/v1/submit' ),
    //     'nonce'   => wp_create_nonce( 'wp_rest' ),
    // ) );
}
add_action( 'wp_enqueue_scripts', 'enqueue_lead_collector_frontend_script' );

Note on Build Process: The above JavaScript assumes you have a build process (like Webpack with Babel) set up to compile ES6+ JavaScript and SCSS into browser-compatible files in a build directory. You would typically run npm install and then npm run build (or similar commands defined in your package.json).

IV. Styling and Refinements

Add CSS for both the editor and frontend to ensure a consistent look and feel. Create src/style.scss for shared styles, src/editor.scss for editor-specific styles, and potentially a build/style-index.css for frontend styles.

Example Styles (src/style.scss)

.wp-block-custom-lead-collector-lead-form {
    border: 1px solid #ccc;
    padding: 20px;
    margin-bottom: 20px;
    background-color: #f9f9f9;
    border-radius: 5px;

    h2 {
        margin-top: 0;
        color: #333;
        text-align: center;
        margin-bottom: 20px;
    }

    .lead-form-fields {
        display: flex;
        flex-direction: column;
        gap: 15px;
        margin-bottom: 20px;

        input[type="text"],
        input[type="email"],
        textarea {
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 3px;
            width: 100%;
            box-sizing: border-box;
        }

        textarea {
            min-height: 100px;
            resize: vertical;
        }
    }

    button {
        display: block;
        width: 100%;
        padding: 12px 20px;
        background-color: #0073aa;
        color: white;
        border: none;
        border-radius: 3px;
        cursor: pointer;
        font-size: 16px;
        transition: background-color 0.3s ease;

        &:hover {
            background-color: #005177;
        }

        &:disabled {
            background-color: #ccc;
            cursor: not-allowed;
        }
    }

    .lead-form-response {
        margin-top: 15px;
        text-align: center;
        font-weight: bold;
    }
}

Example Editor Styles (src/editor.scss)

.wp-block-custom-lead-collector-lead-form {
    // Editor-specific styles, e.g., to make placeholders visible
    input[type="text"],
    input[type="email"],
    textarea {
        background-color: #fff; // Ensure inputs are visible in editor
    }

    // Styles to indicate disabled state in editor preview
    input[type="text"]:disabled,
    input[type="email"]:disabled,
    textarea:disabled {
        background-color: #e0e0e0;
        opacity: 0.7;
    }
}

V. Production Considerations and Enhancements

For a production environment, consider the following:

  • Security: Implement robust server-side validation and sanitization. Use nonces for REST API requests to prevent CSRF attacks, especially if the endpoint requires authentication. The permission_callback in register_rest_route should be carefully configured.
  • Error Handling: Provide more specific error messages to the user and log detailed errors on the server-side for debugging.
  • Data Storage: Instead of just returning a success message, store lead data persistently. This could involve creating custom database tables, using the WordPress Options API, or leveraging custom post types.
  • Email Notifications: Implement email notifications to administrators upon successful lead submission.
  • Rate Limiting: Protect your API endpoint from abuse by implementing rate limiting on the server-side.
  • Accessibility: Ensure the form is accessible, including proper ARIA attributes and keyboard navigation.
  • Internationalization (i18n): Use WordPress i18n functions (__(), _x(), etc.) for all user-facing strings in both PHP and JavaScript.
  • Build Optimization: Configure your build process for production, including minification, code splitting, and asset hashing.
  • User Feedback: Provide clear visual feedback to the user during submission (loading states, success/error messages).

By following these steps, you can create a powerful, custom lead collection mechanism integrated directly into your WordPress content using Gutenberg and the REST API.

Primary Sidebar

A little about the Author

Having 12+ Years of Experience in Software Development, Vinay is a principal software architect, senior systems engineer, and elite technical consultant. He specializes in bespoke PHP/WordPress development, high-performance Magento 2 & Shopify architectures, custom plugin/theme development from scratch, and legacy code modernization (including VB6, VB.NET, PyQt, and Crystal Reports). Known for solving complex database bottlenecks, speed optimization (Core Web Vitals), and advanced security code auditing, Vinay engineers production-ready systems designed to scale under heavy concurrent load conditions.



Chat on WhatsApp

Recent Posts

  • How to analyze and reduce CPU consumption of custom Singleton Registry Pattern event mediators
  • How to analyze and reduce CPU consumption of custom Factory Method design structures event mediators
  • WordPress Development Recipe: High-efficiency server-side rendering for Gutenberg blocks using Readonly classes
  • How to securely integrate SendGrid transactional mailer endpoints into WordPress custom plugins using Filesystem API
  • How to design secure Algolia Search API webhook listeners using signature validation and payload queues

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (658)
  • Desktop Applications (14)
  • DevOps (7)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (4)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (872)
  • PHP (5)
  • PHP Development (42)
  • Plugins & Themes (244)
  • Programming Languages (9)
  • Python (20)
  • Ruby on Rails (1)
  • Security & Compliance (639)
  • SEO & Growth (492)
  • Server (23)
  • Ubuntu (9)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (22)
  • WordPress Plugin Development (114)
  • WordPress Plugin Development (123)
  • WordPress Plugin Development (330)
  • WordPress Theme Development (357)

Recent Posts

  • How to analyze and reduce CPU consumption of custom Singleton Registry Pattern event mediators
  • How to analyze and reduce CPU consumption of custom Factory Method design structures event mediators
  • WordPress Development Recipe: High-efficiency server-side rendering for Gutenberg blocks using Readonly classes

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (872)
  • Debugging & Troubleshooting (658)
  • Security & Compliance (639)
  • SEO & Growth (492)
  • Business & Monetization (390)

Our Products

  • ERP & LMS Systems (4)
  • Directories & Marketplaces (4)
  • Healthcare Portals (3)
  • Point of Sale (POS) (2)
  • E-Commerce Engines (2)

Our Services

  • E-Commerce Development (10)
  • WordPress Development (8)
  • Python & Desktop GUI (7)
  • General Consulting (7)
  • Legacy Modernization (5)
  • Mobile App Development (4)

Copyright © 2026 · Vinay Vengala