• 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 HTMX dynamic attributes

Step-by-Step Guide to building a custom dynamic lead collector block for Gutenberg using HTMX dynamic attributes

Gutenberg Block Architecture for Dynamic Lead Collection

Building a custom Gutenberg block for dynamic lead collection requires a robust architecture that leverages modern web technologies for a seamless user experience. This guide focuses on creating a block that can submit lead data without a full page reload, enhancing conversion rates. We’ll utilize HTMX for its ability to trigger server-side requests directly from HTML attributes, simplifying client-side JavaScript and enabling dynamic updates.

Setting Up the WordPress Plugin and Block Structure

First, we need a basic WordPress plugin structure to house our Gutenberg block. This involves creating a main plugin file and a directory for our block’s assets.

Plugin File (`dynamic-lead-collector.php`)

<?php
/**
 * Plugin Name: Dynamic Lead Collector
 * Description: A custom Gutenberg block for dynamic lead collection using HTMX.
 * Version: 1.0.0
 * Author: Your Name
 * License: GPL-2.0+
 * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
 * Text Domain: dynamic-lead-collector
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit; // Exit if accessed directly.
}

/**
 * Register the block.
 */
function dlc_register_block() {
    // Automatically load dependencies and version
    $asset_file = include( plugin_dir_path( __FILE__ ) . 'build/index.asset.php');

    wp_register_script(
        'dlc-block-editor-script',
        plugins_url( 'build/index.js', __FILE__ ),
        $asset_file['dependencies'],
        $asset_file['version']
    );

    wp_register_style(
        'dlc-block-editor-style',
        plugins_url( 'build/index.css', __FILE__ ),
        array( 'wp-edit-blocks' ),
        $asset_file['version']
    );

    register_block_type( 'dlc/lead-collector', array(
        'editor_script' => 'dlc-block-editor-script',
        'editor_style'  => 'dlc-block-editor-style',
        'render_callback' => 'dlc_render_lead_collector_block',
        'attributes' => array(
            'formTitle' => array(
                'type' => 'string',
                'default' => 'Get in Touch',
            ),
            'submitButtonText' => array(
                'type' => 'string',
                'default' => 'Submit',
            ),
        ),
    ) );
}
add_action( 'init', 'dlc_register_block' );

/**
 * Server-side rendering callback for the block.
 *
 * @param array $attributes Block attributes.
 * @return string HTML output of the block.
 */
function dlc_render_lead_collector_block( $attributes ) {
    $form_title = $attributes['formTitle'] ?? 'Get in Touch';
    $submit_button_text = $attributes['submitButtonText'] ?? 'Submit';

    // Enqueue HTMX script for frontend
    wp_enqueue_script( 'htmx-script', 'https://unpkg.com/[email protected]', array(), '1.9.10', true );

    ob_start();
    ?>
    

'Invalid input. Please provide a valid name and email.' ), 400 ); wp_die(); } // In a real-world scenario, you would save this to the database, send an email, etc. // For this example, we'll just simulate a success message. $response_message = sprintf( '

Thank you, %s! Your message has been received.

', esc_html( $name ) ); echo $response_message; // HTMX will swap this into the hx-target element. wp_die(); // This is required for AJAX to work. } add_action( 'wp_ajax_dlc_submit_lead', 'dlc_ajax_submit_lead' ); add_action( 'wp_ajax_nopriv_dlc_submit_lead', 'dlc_ajax_submit_lead' ); // For logged-out users // Add nonce to the form dynamically if needed for more robust security // For simplicity in this example, we rely on sanitization and basic checks. // A more advanced approach would involve dynamically adding a nonce field.

This PHP file registers the block, defines its attributes (like form title and button text), and sets up a server-side rendering callback. Crucially, it enqueues the HTMX library and defines an AJAX action hook for processing lead submissions.

Gutenberg Block Editor Implementation (JavaScript/React)

Next, we’ll create the JavaScript file that defines the block’s behavior within the Gutenberg editor. This uses React and the WordPress Block Editor API.

Block Editor Script (`src/index.js`)

import { registerBlockType } from '@wordpress/blocks';
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
import { PanelBody, TextControl } from '@wordpress/components';
import './style.scss'; // For frontend styles
import './editor.scss'; // For editor styles

const Edit = ( { attributes, setAttributes } ) => {
    const blockProps = useBlockProps();
    const { formTitle, submitButtonText } = attributes;

    const onChangeFormTitle = ( newTitle ) => {
        setAttributes( { formTitle: newTitle } );
    };

    const onChangeSubmitButtonText = ( newText ) => {
        setAttributes( { submitButtonText: newText } );
    };

    // Preview of the form in the editor
    return (
        <>
            <InspectorControls>
                <PanelBody title="Lead Collector Settings" initialOpen={ true }>
                    <TextControl
                        label="Form Title"
                        value={ formTitle }
                        onChange={ onChangeFormTitle }
                    />
                    <TextControl
                        label="Submit Button Text"
                        value={ submitButtonText }
                        onChange={ onChangeSubmitButtonText }
                    />
                </PanelBody>
            </InspectorControls>
            <div { ...blockProps } className="dlc-lead-collector">
                <h2>{ formTitle || 'Get in Touch' }</h2>
                <div className="dlc-form-group">
                    <label>Name:</label>
                    <input type="text" placeholder="Enter your name" disabled />
                </div>
                <div className="dlc-form-group">
                    <label>Email:</label>
                    <input type="email" placeholder="Enter your email" disabled />
                </div>
                <div className="dlc-form-group">
                    <label>Message:</label>
                    <textarea rows="4" placeholder="Your message" disabled></textarea>
                </div>
                <button type="button" disabled>{ submitButtonText || 'Submit' }</button>
                <p>(Form submission is disabled in the editor preview)</p>
            </div>
        </>
    );
};

registerBlockType( 'dlc/lead-collector', {
    apiVersion: [ 2, 2 ],
    title: 'Dynamic Lead Collector',
    icon: 'email-alt', // WordPress dashicon
    category: 'widgets',
    attributes: {
        formTitle: {
            type: 'string',
            default: 'Get in Touch',
        },
        submitButtonText: {
            type: 'string',
            default: 'Submit',
        },
    },
    edit: Edit,
    save: () => null, // We use a server-side render_callback, so save returns null.
} );

This script registers the block, defines its editable attributes using `InspectorControls` (for the sidebar settings), and provides a visual representation in the editor. The `save: () => null` is crucial because the block’s output is handled entirely by the PHP `render_callback` on the frontend.

Styling the Lead Collector Block

We need some basic CSS for both the editor and the frontend to make the form presentable.

Editor Styles (`src/editor.scss`)

.wp-block-dlc-lead-collector {
    border: 1px dashed #ccc;
    padding: 15px;
    background-color: #f9f9f9;
    margin-bottom: 20px;

    h2 {
        margin-top: 0;
        color: #333;
    }

    .dlc-form-group {
        margin-bottom: 15px;
        label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
        }
        input[type="text"],
        input[type="email"],
        textarea {
            width: 100%;
            padding: 8px;
            border: 1px solid #ddd;
            box-sizing: border-box;
        }
        textarea {
            resize: vertical;
        }
    }

    button {
        padding: 10px 15px;
        background-color: #0073aa;
        color: white;
        border: none;
        cursor: pointer;
        &:disabled {
            background-color: #ccc;
        }
    }
}

Frontend Styles (`src/style.scss`)

.dlc-lead-collector {
    border: 1px solid #e0e0e0;
    padding: 25px;
    background-color: #ffffff;
    border-radius: 8px;
    box-shadow: 0 2px 5px rgba(0,0,0,0.1);
    max-width: 500px;
    margin: 20px auto;
    font-family: sans-serif;

    h2 {
        text-align: center;
        margin-top: 0;
        margin-bottom: 25px;
        color: #333;
        font-size: 1.8em;
    }

    .dlc-form-group {
        margin-bottom: 20px;
        label {
            display: block;
            margin-bottom: 8px;
            font-weight: 600;
            color: #555;
        }
        input[type="text"],
        input[type="email"],
        textarea {
            width: 100%;
            padding: 12px;
            border: 1px solid #ccc;
            border-radius: 4px;
            box-sizing: border-box;
            font-size: 1em;
            &:focus {
                border-color: #0073aa;
                outline: none;
                box-shadow: 0 0 0 2px rgba(0, 115, 170, 0.2);
            }
        }
        textarea {
            resize: vertical;
            min-height: 100px;
        }
    }

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

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

    .dlc-response-message {
        margin-top: 20px;
        padding: 15px;
        border-radius: 4px;
        text-align: center;
        font-size: 1em;
        & > p {
            margin: 0;
        }
    }
}

These SCSS files will be compiled into CSS during the build process.

Build Process and Plugin Activation

To compile the JavaScript and SCSS, you’ll need Node.js and npm (or yarn) installed. We’ll use `@wordpress/scripts` for a streamlined build process.

`package.json` Configuration

{
  "name": "dynamic-lead-collector",
  "version": "1.0.0",
  "description": "A custom Gutenberg block for dynamic lead collection using HTMX.",
  "main": "build/index.js",
  "scripts": {
    "build": "wp-scripts build",
    "start": "wp-scripts start"
  },
  "keywords": [
    "wordpress",
    "gutenberg",
    "block",
    "htmx",
    "lead collection"
  ],
  "author": "Your Name",
  "license": "GPL-2.0-or-later",
  "devDependencies": {
    "@wordpress/scripts": "^26.10.0"
  }
}

Run the following commands in your plugin’s root directory:

npm install
npm run build

This will create the `build/` directory containing `index.js`, `index.css`, and `index.asset.php`. Activate the “Dynamic Lead Collector” plugin in your WordPress admin area.

Implementing HTMX Dynamic Attributes

The core of the dynamic behavior lies in the HTMX attributes added to the form in the PHP `dlc_render_lead_collector_block` function:

<form id="dlc-lead-form"
      hx-post=""
      hx-target="#dlc-response-message"
      hx-swap="innerHTML"
      hx-trigger="submit">
    <input type="hidden" name="action" value="dlc_submit_lead">
    <!-- ... form fields ... -->
</form>
<div id="dlc-response-message"></div>
  • hx-post: Specifies the URL to send the POST request to. We use `admin-ajax.php` for WordPress AJAX requests.
  • hx-target: Defines the HTML element that will receive the response from the server. Here, it’s the `div` with the ID `dlc-response-message`.
  • hx-swap: Determines how the response content is placed into the target element. innerHTML replaces the existing content.
  • hx-trigger="submit": Configures HTMX to trigger the request when the form is submitted.
  • <input type="hidden" name="action" value="dlc_submit_lead">: This hidden input is essential for WordPress AJAX to identify which function to call.

When the form is submitted:

  1. HTMX intercepts the submit event.
  2. It sends a POST request to `wp-admin/admin-ajax.php` with the form data.
  3. The `action=dlc_submit_lead` parameter tells WordPress to execute our `dlc_ajax_submit_lead` function.
  4. Our PHP function processes the data, generates a response message (e.g., “Thank you!”), and echoes it.
  5. HTMX receives this response and injects it into the element with `id=”dlc-response-message”`.

Enhancing Security and User Feedback

While the example includes basic sanitization (`sanitize_text_field`, `sanitize_email`, `sanitize_textarea_field`), robust security is paramount. For AJAX requests, WordPress typically uses nonces. Although HTMX doesn’t natively send a nonce field, you could dynamically add one using JavaScript before submission or rely on the `check_ajax_referer` within your PHP function if you were to manually add a nonce field to the form.

The `hx-target` and `hx-swap` attributes provide immediate visual feedback to the user. Instead of a full page refresh, they see the success or error message appear directly below the form, creating a smoother, more app-like experience.

Advanced Considerations and Next Steps

For production environments, consider:

  • Data Persistence: Integrate with WordPress custom post types, user meta, or a dedicated database table to store lead information.
  • Email Notifications: Send lead details to an administrator’s email address using `wp_mail()`.
  • Error Handling: Implement more sophisticated error handling on both the client (HTMX) and server (PHP) sides. Display specific error messages for invalid fields.
  • Form Validation: While basic HTML5 validation is present, consider server-side validation for critical data integrity.
  • Rate Limiting: Protect against brute-force submissions by implementing rate limiting on the AJAX endpoint.
  • Accessibility: Ensure the form and feedback messages are accessible to users with disabilities.
  • Internationalization: Use WordPress internationalization functions (`__`, `_e`, etc.) for all user-facing strings.
  • Dynamic Content Loading: HTMX can also be used to load different parts of the form or response based on user interaction, further enhancing dynamism. For instance, `hx-get` could load a follow-up question after initial submission.

By combining Gutenberg’s block architecture with HTMX’s declarative approach to AJAX, you can build highly interactive and efficient components for your WordPress site without complex JavaScript frameworks.

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

  • Step-by-Step Guide to building a custom secure file encryption vault block for Gutenberg using Vanilla JS Web Components
  • How to securely integrate Slack Webhooks integration endpoints into WordPress custom plugins using Cron API (wp_schedule_event)
  • Implementing automated compliance reporting for custom user transaction ledgers ledgers using native PHP ZipArchive streams
  • Step-by-Step Guide to building a custom secure file encryption vault block for Gutenberg using REST API custom routes
  • Step-by-Step Guide to building a custom bulk image watermarker block for Gutenberg using Svelte standalone templates

Categories

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

Recent Posts

  • Step-by-Step Guide to building a custom secure file encryption vault block for Gutenberg using Vanilla JS Web Components
  • How to securely integrate Slack Webhooks integration endpoints into WordPress custom plugins using Cron API (wp_schedule_event)
  • Implementing automated compliance reporting for custom user transaction ledgers ledgers using native PHP ZipArchive streams

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (817)
  • Debugging & Troubleshooting (602)
  • Security & Compliance (577)
  • 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