Step-by-Step Guide to building a custom custom analytics tracker block for Gutenberg using HTMX dynamic attributes
Setting Up Your WordPress Development Environment
Before we dive into building the custom Gutenberg block, ensure you have a local WordPress development environment set up. This typically involves:
- A local web server (e.g., Local by Flywheel, MAMP, XAMPP, Docker with a WordPress image).
- PHP 7.4+ and MySQL 5.7+ (or MariaDB equivalent).
- Node.js and npm (or yarn) for JavaScript build processes.
- A code editor (e.g., VS Code, Sublime Text).
For this tutorial, we’ll assume you’re comfortable with basic WordPress plugin development and the command line.
Plugin Structure and Initialization
Create a new directory for your plugin in the wp-content/plugins/ folder. Let’s call it htmx-analytics-tracker. Inside this directory, create the main plugin file, htmx-analytics-tracker.php.
Add the standard WordPress plugin header to htmx-analytics-tracker.php:
<?php
/**
* Plugin Name: HTMX Analytics Tracker
* Description: A custom Gutenberg block to track user interactions using HTMX.
* Version: 1.0.0
* Author: Your Name
* License: GPL-2.0-or-later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: htmx-analytics-tracker
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Enqueue block assets.
*/
function htmx_analytics_tracker_enqueue_block_assets() {
// Enqueue the block editor script.
wp_enqueue_script(
'htmx-analytics-tracker-editor-script',
plugin_dir_url( __FILE__ ) . 'build/index.js',
array( 'wp-blocks', 'wp-element', 'wp-editor', 'wp-components', 'wp-i18n' ),
filemtime( plugin_dir_path( __FILE__ ) . 'build/index.js' )
);
// Enqueue the block editor styles.
wp_enqueue_style(
'htmx-analytics-tracker-editor-style',
plugin_dir_url( __FILE__ ) . 'build/index.css',
array( 'wp-edit-blocks' ),
filemtime( plugin_dir_path( __FILE__ ) . 'build/index.css' )
);
// Enqueue frontend styles and scripts (including HTMX).
wp_enqueue_script(
'htmx-analytics-tracker-frontend-script',
plugin_dir_url( __FILE__ ) . 'build/frontend.js',
array(), // No dependencies for HTMX itself, but you might add others.
filemtime( plugin_dir_path( __FILE__ ) . 'build/frontend.js' ),
true // Load in footer
);
wp_enqueue_style(
'htmx-analytics-tracker-frontend-style',
plugin_dir_url( __FILE__ ) . 'build/frontend.css',
array(),
filemtime( plugin_dir_path( __FILE__ ) . 'build/frontend.css' )
);
// Localize script for passing data to JavaScript.
wp_localize_script( 'htmx-analytics-tracker-frontend-script', 'htmxAnalyticsTracker', array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
// Add any other data you need to pass to the frontend.
) );
}
add_action( 'enqueue_block_assets', 'htmx_analytics_tracker_enqueue_block_assets' );
/**
* Register the custom block.
*/
function htmx_analytics_tracker_register_block() {
register_block_type( __DIR__ );
}
add_action( 'init', 'htmx_analytics_tracker_register_block' );
/**
* Handle AJAX requests for tracking.
*/
function htmx_analytics_tracker_handle_ajax() {
check_ajax_referer( 'htmx_analytics_tracker_nonce', 'nonce' );
$event_type = isset( $_POST['event_type'] ) ? sanitize_text_field( $_POST['event_type'] ) : '';
$element_id = isset( $_POST['element_id'] ) ? sanitize_text_field( $_POST['element_id'] ) : '';
$user_id = get_current_user_id(); // Get logged-in user ID, or 0 if not logged in.
if ( ! empty( $event_type ) && ! empty( $element_id ) ) {
// In a real-world scenario, you would log this data to a database,
// a log file, or an external analytics service.
// For this example, we'll just log it to the PHP error log.
error_log( sprintf(
'HTMX Analytics Event: Type="%s", ElementID="%s", UserID="%d", Timestamp="%s"',
$event_type,
$element_id,
$user_id,
current_time( 'mysql' )
) );
wp_send_json_success( array( 'message' => 'Event tracked successfully.' ) );
} else {
wp_send_json_error( array( 'message' => 'Invalid event data.' ) );
}
}
add_action( 'wp_ajax_track_analytics_event', 'htmx_analytics_tracker_handle_ajax' );
add_action( 'wp_ajax_nopriv_track_analytics_event', 'htmx_analytics_tracker_handle_ajax' ); // For non-logged-in users
Frontend JavaScript and HTMX Integration
We need to enqueue HTMX and our custom frontend JavaScript. Create a src directory in your plugin folder. Inside src, create frontend.js and frontend.css. Also, create a block.json file to define your Gutenberg block.
First, let’s set up the build process. Install the necessary npm packages:
cd htmx-analytics-tracker npm init -y npm install @wordpress/scripts @wordpress/blocks @wordpress/element @wordpress/i18n --save-dev npm install htmx.org --save
Add a build script to your package.json:
{
"name": "htmx-analytics-tracker",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "wp-scripts build",
"start": "wp-scripts start"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@wordpress/blocks": "^12.10.0",
"@wordpress/components": "^26.7.0",
"@wordpress/element": "^5.7.0",
"@wordpress/i18n": "^4.40.0",
"@wordpress/scripts": "^26.10.0"
},
"dependencies": {
"htmx.org": "^1.9.10"
}
}
Now, create the src/frontend.js file. This script will handle the HTMX requests to our AJAX handler.
// Import HTMX.
import htmx from 'htmx.org';
// Ensure HTMX is initialized.
document.addEventListener('DOMContentLoaded', () => {
// HTMX is globally available after import, or you can access it via `htmx`.
// No explicit initialization needed if imported this way.
// Example: Add a global event listener for HTMX requests to track them.
document.body.addEventListener('htmx:afterRequest', function(evt) {
// Check if the request was successful and if it's our tracker event.
if (evt.detail.xhr.status >= 200 && evt.detail.xhr.status < 300 && evt.target.dataset.htmxAnalyticsEvent) {
const eventData = evt.target.dataset;
const eventType = eventData.htmxAnalyticsEvent;
const elementId = eventData.htmxAnalyticsElementId || evt.target.id || 'unknown';
// Send the event to our WordPress AJAX handler.
// We use fetch here for simplicity, but you could also use HTMX itself
// to POST to the AJAX URL if you prefer.
fetch(htmxAnalyticsTracker.ajax_url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
action: 'track_analytics_event',
nonce: document.querySelector('meta[name="csrf-token"]')?.content || '', // Example nonce, adjust as needed
event_type: eventType,
element_id: elementId,
}),
})
.then(response => response.json())
.then(data => {
console.log('Analytics event tracked:', data);
})
.catch((error) => {
console.error('Error tracking analytics event:', error);
});
}
});
// You can also add specific event listeners for elements if needed.
// For example, if you have a button that triggers an action and you want to track clicks.
document.querySelectorAll('[data-htmx-analytics-track-click]').forEach(element => {
element.addEventListener('click', function() {
const eventType = this.dataset.htmxAnalyticsTrackClick;
const elementId = this.id || 'unknown';
fetch(htmxAnalyticsTracker.ajax_url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
action: 'track_analytics_event',
nonce: document.querySelector('meta[name="csrf-token"]')?.content || '', // Example nonce, adjust as needed
event_type: eventType,
element_id: elementId,
}),
})
.then(response => response.json())
.then(data => {
console.log('Click event tracked:', data);
})
.catch((error) => {
console.error('Error tracking click event:', error);
});
});
});
});
Create src/frontend.css for any frontend styling:
/* Add any frontend styles for your block here */
.wp-block-htmx-analytics-tracker-tracker-block {
border: 1px solid #ccc;
padding: 15px;
margin-bottom: 15px;
background-color: #f9f9f9;
}
Gutenberg Block Registration and Editor Interface
Create block.json in the root of your plugin directory. This file describes your block to Gutenberg.
{
"apiVersion": 2,
"name": "htmx-analytics-tracker/tracker-block",
"title": "HTMX Analytics Tracker",
"category": "widgets",
"icon": "chart-bar",
"description": "A custom block to track user interactions using HTMX.",
"keywords": ["analytics", "htmx", "tracker", "tracking"],
"attributes": {
"trackingEventName": {
"type": "string",
"default": "user_interaction"
},
"trackedElementId": {
"type": "string",
"default": ""
},
"triggerEvent": {
"type": "string",
"default": "click"
}
},
"editorScript": "file:./build/index.js",
"editorStyle": "file:./build/index.css",
"style": "file:./build/frontend.css",
"render": "file:./render.php"
}
Now, create the JavaScript for the block editor interface. Create src/index.js:
import { registerBlockType } from '@wordpress/blocks';
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
import { PanelBody, TextControl, SelectControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import './frontend.css'; // Import frontend styles for editor preview
registerBlockType('htmx-analytics-tracker/tracker-block', {
title: __('HTMX Analytics Tracker', 'htmx-analytics-tracker'),
icon: 'chart-bar',
category: 'widgets',
attributes: {
trackingEventName: {
type: 'string',
default: 'user_interaction',
},
trackedElementId: {
type: 'string',
default: '',
},
triggerEvent: {
type: 'string',
default: 'click',
},
},
edit: ({ attributes, setAttributes }) => {
const blockProps = useBlockProps();
const { trackingEventName, trackedElementId, triggerEvent } = attributes;
const onTrackingEventNameChange = (newValue) => {
setAttributes({ trackingEventName: newValue });
};
const onTrackedElementIdChange = (newValue) => {
setAttributes({ trackedElementId: newValue });
};
const onTriggerEventChange = (newValue) => {
setAttributes({ triggerEvent: newValue });
};
return (
<>
{__('Analytics Tracker Block', 'htmx-analytics-tracker')}
{__('Event:', 'htmx-analytics-tracker')} {trackingEventName}
{trackedElementId && (
{__('Element ID:', 'htmx-analytics-tracker')} {trackedElementId}
)}
{__('Trigger:', 'htmx-analytics-tracker')} {triggerEvent}
{__('This block will trigger an analytics event when the specified trigger occurs.', 'htmx-analytics-tracker')}
>
);
},
save: ({ attributes }) => {
const blockProps = useBlockProps.save();
const { trackingEventName, trackedElementId, triggerEvent } = attributes;
// Construct HTMX attributes dynamically
let htmxAttributes = {};
htmxAttributes['data-htmx-analytics-event'] = trackingEventName;
if (trackedElementId) {
htmxAttributes['data-htmx-analytics-element-id'] = trackedElementId;
}
// Add the trigger event listener using HTMX's `hx-trigger`
// We'll use a generic element that listens for the specified trigger.
// The actual element being tracked might be a child or the block itself.
// For simplicity, we'll attach it to the main block div.
// A more complex setup might involve targeting specific child elements.
// The actual tracking logic will be in frontend.js, which listens for htmx:afterRequest
// or specific DOM events. Here we just set up the data attributes.
return (
{/* Content for the frontend */}
Analytics Tracker (Frontend)
Event: {trackingEventName}
{trackedElementId && Element ID: {trackedElementId}
}
Trigger: {triggerEvent}
);
},
});
Create src/index.css for editor-specific styles:
/* Styles for the block editor */
.wp-block-htmx-analytics-tracker-tracker-block {
border: 1px dashed #999;
padding: 10px;
background-color: #e0f0ff;
text-align: center;
}
Rendering the Block on the Frontend
Create a render.php file in your plugin’s root directory. This file defines how the block is rendered on the frontend. We’ll use the dynamic attributes to configure HTMX.
<?php
/**
* Block render template.
*
* @package htmx-analytics-tracker
*/
$tracking_event_name = isset( $attributes['trackingEventName'] ) ? esc_attr( $attributes['trackingEventName'] ) : 'user_interaction';
$tracked_element_id = isset( $attributes['trackedElementId'] ) ? esc_attr( $attributes['trackedElementId'] ) : '';
$trigger_event = isset( $attributes['triggerEvent'] ) ? esc_attr( $attributes['triggerEvent'] ) : 'click';
// Generate a unique ID if none is provided for the element to be tracked.
$element_id_attr = ! empty( $tracked_element_id ) ? $tracked_element_id : 'tracker-block-' . uniqid();
// The block's wrapper attributes.
$wrapper_attributes = get_block_wrapper_attributes();
// Construct HTMX attributes.
// We'll use data attributes that our frontend.js will pick up.
// The actual HTMX request will be handled by frontend.js listening to DOM events.
// For demonstration, we'll add a simple placeholder element that can be targeted.
?>
<div
<?php echo $wrapper_attributes; ?>
id="<?php echo esc_attr( $element_id_attr ); ?>"
data-htmx-analytics-event="<?php echo $tracking_event_name; ?>"
data-htmx-analytics-element-id="<?php echo esc_attr( $element_id_attr ); ?>"
data-htmx-analytics-trigger="<?php echo $trigger_event; ?>"
>
<p>Analytics Tracker Block (Frontend)</p>
<p>Event: <strong><?php echo $tracking_event_name; ?></strong></p>
<?php if ( ! empty( $tracked_element_id ) ) : ?>
<p>Element ID: <strong><?php echo $tracked_element_id; ?></strong></p>
</?php endif; ?>
<p>Trigger: <strong><?php echo $trigger_event; ?></strong></p>
<!-- Example of an element that might be tracked -->
<button
id="example-button-<?php echo esc_attr( $element_id_attr ); ?>"
data-htmx-analytics-track-click="<?php echo $tracking_event_name; ?>-button"
data-htmx-analytics-element-id="example-button-<?php echo esc_attr( $element_id_attr ); ?>"
style="padding: 10px; margin-top: 10px; cursor: pointer;"
>
Click Me to Track
</button>
</div>
In frontend.js, we need to refine the event listening to use the `data-htmx-analytics-trigger` attribute. Let’s update the `frontend.js` to dynamically attach event listeners based on the `triggerEvent` attribute.
import htmx from 'htmx.org';
document.addEventListener('DOMContentLoaded', () => {
const ajaxUrl = htmxAnalyticsTracker.ajax_url;
const nonce = document.querySelector('meta[name="csrf-token"]')?.content || ''; // Ensure you have a nonce mechanism if needed
// Function to send analytics event
const sendAnalyticsEvent = (eventType, elementId) => {
fetch(ajaxUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
action: 'track_analytics_event',
nonce: nonce,
event_type: eventType,
element_id: elementId,
}),
})
.then(response => response.json())
.then(data => {
console.log('Analytics event tracked:', data);
})
.catch((error) => {
console.error('Error tracking analytics event:', error);
});
};
// Listen for HTMX requests to potentially track them
document.body.addEventListener('htmx:afterRequest', function(evt) {
if (evt.detail.xhr.status >= 200 && evt.detail.xhr.status < 300) {
const targetElement = evt.target;
if (targetElement.dataset.htmxAnalyticsEvent) {
const eventType = targetElement.dataset.htmxAnalyticsEvent;
const elementId = targetElement.dataset.htmxAnalyticsElementId || targetElement.id || 'unknown';
sendAnalyticsEvent(eventType, elementId);
}
}
});
// Dynamically attach event listeners based on data-htmx-analytics-trigger
document.querySelectorAll('[data-htmx-analytics-event]').forEach(element => {
const trigger = element.dataset.htmxAnalyticsTrigger || 'click'; // Default to click
const eventType = element.dataset.htmxAnalyticsEvent;
const elementId = element.dataset.htmxAnalyticsElementId || element.id || 'unknown';
// Remove existing listeners to prevent duplicates if the DOM is updated
// This is a simplified approach; a more robust solution might involve
// event delegation or a more sophisticated listener management.
if (trigger === 'click') {
element.addEventListener('click', function() {
sendAnalyticsEvent(eventType, elementId);
});
} else if (trigger === 'hover') {
element.addEventListener('mouseenter', function() {
sendAnalyticsEvent(eventType + '_hover', elementId); // Append _hover for clarity
});
} else if (trigger === 'focus') {
element.addEventListener('focus', function() {
sendAnalyticsEvent(eventType + '_focus', elementId); // Append _focus for clarity
});
}
// Add more trigger types as needed
});
// Specific listener for the example button in render.php
document.querySelectorAll('[data-htmx-analytics-track-click]').forEach(button => {
button.addEventListener('click', function() {
const eventType = this.dataset.htmxAnalyticsTrackClick;
const elementId = this.dataset.htmxAnalyticsElementId || this.id || 'unknown';
sendAnalyticsEvent(eventType, elementId);
});
});
});
Building the Assets
Navigate to your plugin’s root directory in the terminal and run the build command:
cd htmx-analytics-tracker npm run build
This command will compile your JavaScript and CSS files from the src directory into the build directory, as specified in block.json and your main plugin file.
Testing and Verification
Activate your htmx-analytics-tracker plugin in the WordPress admin area. Then, create a new post or page and add the “HTMX Analytics Tracker” block. Configure the tracking event name and element ID in the block inspector.
Publish the post/page and view it on the frontend. Interact with the block (e.g., click the “Click Me to Track” button if you added it). Check your server’s PHP error log (or wherever you’ve configured error logging) for messages like:
[timestamp] HTMX Analytics Event: Type="user_interaction-button", ElementID="example-button-...", UserID="1", Timestamp="..."
You can also use your browser’s developer console to see the network requests being made to admin-ajax.php and any console logs from your frontend.js script.
Further Enhancements
- Data Storage: Instead of logging to the error log, implement proper database storage for analytics events. Create custom database tables or use existing WordPress options/post meta if appropriate.
- External Services: Integrate with third-party analytics platforms (e.g., Google Analytics, Matomo) by sending data via their respective APIs.
- More Trigger Types: Expand the supported trigger events in
frontend.js(e.g., scroll, form submission). - Advanced HTMX Features: Leverage more of HTMX’s capabilities, such as swapping content based on events, to create more dynamic tracking scenarios.
- Security: Ensure robust nonce verification and input sanitization for all AJAX requests.
- User Interface: Develop a dedicated admin page to view and manage the tracked analytics data.