How to build custom Timber Twig templating engines extensions utilizing modern Heartbeat API schemas
Leveraging the Heartbeat API for Dynamic Timber/Twig Extensions
WordPress’s Heartbeat API, often overlooked beyond its basic AJAX polling for autosave and session management, presents a powerful, albeit underutilized, mechanism for real-time data synchronization and dynamic content manipulation within themes and plugins. When combined with Timber and its Twig templating engine, the Heartbeat API unlocks sophisticated patterns for building highly interactive and responsive user interfaces without resorting to full page reloads or complex JavaScript frameworks. This post details how to architect and implement custom Timber/Twig extensions that react to and leverage Heartbeat events, providing a robust foundation for advanced WordPress development.
Understanding Heartbeat API Schemas and Hooks
The Heartbeat API operates on a request-response cycle, triggered by JavaScript in the browser and handled by PHP on the server. The core interaction points are the heartbeat_send filter and the heartbeat_tick action. The heartbeat_send filter allows us to inject data into the Heartbeat payload sent from the server to the client. Conversely, the heartbeat_tick action is executed on the server when a Heartbeat request is received, providing an opportunity to perform server-side logic based on the incoming data or to prepare data for the next client-side tick.
The data exchanged via Heartbeat is typically structured as an associative array. For custom extensions, it’s crucial to define a clear schema for the data you intend to send and receive. This schema should be documented and consistently applied across your PHP and JavaScript implementations. A common pattern is to prefix your custom data keys with a unique identifier to avoid conflicts with WordPress core or other plugins.
Server-Side Data Injection with heartbeat_send
To inject custom data into the Heartbeat payload, we hook into the heartbeat_send filter. This filter receives the current Heartbeat data array and should return a modified array. This is the primary method for pushing dynamic server-side information to the client for use in Twig templates.
Example: Pushing User-Specific Settings
Let’s imagine a scenario where we want to dynamically update a user’s notification count or a real-time status indicator within a Timber-rendered dashboard. We can push this data via Heartbeat.
PHP Implementation (functions.php or plugin file)
/**
* Inject custom data into the Heartbeat API payload.
*
* @param array $data The current Heartbeat data.
* @return array The modified Heartbeat data.
*/
function my_custom_heartbeat_data( $data ) {
// Ensure we are in the admin area or a specific context where this is relevant.
// For front-end, you'd typically enqueue a script that initiates Heartbeat.
if ( ! is_admin() ) {
// You might want to check for specific page templates or conditions here.
// For example: if ( is_page_template( 'template-dashboard.php' ) ) { ... }
// Or if you've explicitly initialized Heartbeat on the front-end.
}
// Example: Fetching a user-specific notification count.
$user_id = get_current_user_id();
if ( $user_id ) {
// Replace with your actual notification fetching logic.
$notification_count = get_user_meta( $user_id, 'my_custom_notifications', true );
$data['my_plugin_heartbeat']['notification_count'] = intval( $notification_count ?: 0 );
}
// Example: Pushing a real-time status.
$data['my_plugin_heartbeat']['server_status'] = 'online'; // Or fetch dynamic status.
// Add a timestamp to ensure cache busting or for client-side logic.
$data['my_plugin_heartbeat']['timestamp'] = time();
return $data;
}
add_filter( 'heartbeat_send', 'my_custom_heartbeat_data', 10, 1 );
In this example, we’ve added a custom array key my_plugin_heartbeat to the Heartbeat data. This namespace helps prevent collisions. Inside, we push notification_count and server_status. The timestamp is useful for client-side validation or triggering updates.
Client-Side Data Consumption and Twig Integration
On the client-side, WordPress’s core JavaScript enqueues the Heartbeat API. We can hook into the heartbeat-send JavaScript event to intercept the data being sent from the server and then update our Twig templates or trigger other JavaScript logic. Timber provides a convenient way to pass data to Twig, and we can dynamically update this data using JavaScript.
JavaScript Implementation
(function($) {
// Ensure Heartbeat is enabled. It's usually enqueued by default in admin.
// For front-end, you'd need to enqueue the heartbeat script and potentially
// call wp.heartbeat.connect() if not automatically initiated.
if (typeof wp !== 'undefined' && typeof wp.heartbeat !== 'undefined') {
// Hook into the heartbeat-send event
$(document).on('heartbeat-send', function(e, data) {
// Add custom data to be sent to the server (optional for this example)
// data.my_custom_data = { 'action': 'my_plugin_action' };
// The server will send back data via the 'heartbeat-tick' event.
// We'll process that data on the 'heartbeat-tick' event.
});
// Hook into the heartbeat-tick event to receive data from the server
$(document).on('heartbeat-tick', function(e, data) {
// Check if our custom data is present
if (data['my_plugin_heartbeat']) {
var pluginData = data['my_plugin_heartbeat'];
// Update notification count in the DOM if it exists
if (pluginData.notification_count !== undefined) {
var $notificationElement = $('#my-notification-count'); // Assume an element with this ID exists
if ($notificationElement.length) {
$notificationElement.text(pluginData.notification_count);
// You might also want to add/remove classes for visual feedback
if (pluginData.notification_count > 0) {
$notificationElement.addClass('has-notifications');
} else {
$notificationElement.removeClass('has-notifications');
}
}
}
// Update server status
if (pluginData.server_status !== undefined) {
var $statusElement = $('#server-status-indicator'); // Assume an element with this ID exists
if ($statusElement.length) {
$statusElement.text('Status: ' + pluginData.server_status);
// Update class based on status
$statusElement.removeClass('status-online status-offline').addClass('status-' + pluginData.server_status);
}
}
// You can also trigger custom events or update other parts of your UI
// $(document).trigger('my_plugin_heartbeat_updated', [pluginData]);
}
});
// Optional: Handle Heartbeat errors
$(document).on('heartbeat-error', function(e, error) {
console.error('Heartbeat Error:', error);
// Implement fallback or error display logic
});
// Optional: Manually connect to Heartbeat if not automatically initiated
// wp.heartbeat.connect();
}
})(jQuery);
This JavaScript snippet listens for the heartbeat-tick event. When it fires, it checks for our my_plugin_heartbeat data. If found, it updates specific DOM elements (e.g., a notification count display, a status indicator) with the received values. This assumes you have corresponding HTML elements in your Twig template with specific IDs.
Twig Template Integration
In your Timber-rendered Twig template, you’ll need to output the initial state of these elements and ensure they have the correct IDs for the JavaScript to target. The initial values can be passed from your PHP Timber context.
{# Example: dashboard.twig #}
<div class="dashboard-widgets">
<div class="widget notification-widget">
<h3>Notifications</h3>
<p>You have <span id="my-notification-count">{{ initial_notification_count | default(0) }}</span> new notifications.</p>
</div>
<div class="widget server-status-widget">
<h3>Server Status</h3>
<p><span id="server-status-indicator" class="status-{{ initial_server_status | default('unknown') }}">Status: {{ initial_server_status | default('Unknown') }}</span></p>
</div>
</div>
The PHP code that renders this template would pass the initial values:
// In your Timber context rendering function (e.g., in functions.php or a custom controller)
function render_dashboard_page() {
$user_id = get_current_user_id();
$initial_notification_count = $user_id ? get_user_meta( $user_id, 'my_custom_notifications', true ) : 0;
$context = Timber::context();
$context['initial_notification_count'] = intval( $initial_notification_count ?: 0 );
$context['initial_server_status'] = 'online'; // Default or fetch initial status
Timber::render( 'dashboard.twig', $context );
}
Server-Side Logic with heartbeat_tick
While heartbeat_send is for *sending* data, heartbeat_tick is an action that fires *on the server* whenever a Heartbeat request is received. This is useful for performing server-side tasks in response to a client ping, such as background processing, cache invalidation, or updating transient data. You can also use it to *prepare* data for the next heartbeat_send cycle.
Example: Server-Side Data Processing
/**
* Process server-side logic on each Heartbeat tick.
*
* @param array $response The Heartbeat response data.
* @param array $data The data received from the client (if any).
* @return array The modified Heartbeat response data.
*/
function my_custom_heartbeat_tick_handler( $response, $data ) {
// Check if our plugin is sending specific data for processing.
if ( isset( $data['my_plugin_heartbeat'] ) && isset( $data['my_plugin_heartbeat']['action'] ) ) {
$action = $data['my_plugin_heartbeat']['action'];
switch ( $action ) {
case 'reset_notifications':
$user_id = get_current_user_id();
if ( $user_id ) {
delete_user_meta( $user_id, 'my_custom_notifications' );
// Prepare data to be sent back in the next heartbeat_send cycle
// This will be picked up by the heartbeat_send filter.
// We can directly modify global $data if needed, but it's cleaner
// to let heartbeat_send handle the output.
// For immediate feedback, we could add to $response, but it's not the primary mechanism.
}
break;
case 'check_server_health':
// Perform a quick server health check.
$health_status = 'healthy'; // Replace with actual check.
// This status will be available in the next heartbeat_send cycle.
break;
// Add more actions as needed
}
}
// You can also use this tick to update data that will be sent in the *next* heartbeat_send.
// For instance, if a background task completed.
// For simplicity, we'll rely on heartbeat_send for pushing data.
return $response;
}
add_action( 'heartbeat_tick', 'my_custom_heartbeat_tick_handler', 10, 2 );
In this heartbeat_tick handler, we check for a custom action sent from the client (e.g., ‘reset_notifications’). If detected, we perform the server-side operation. Note that the heartbeat_tick handler receives data *from* the client and returns data *to* the client’s Heartbeat response. However, for pushing dynamic data *to* the Twig template, it’s generally more idiomatic to use the heartbeat_send filter, as it directly populates the data sent to the client for rendering.
Advanced Patterns and Considerations
Heartbeat Intervals and Performance
The Heartbeat API has configurable intervals. By default, it polls every 15-60 seconds in the admin and every 60 seconds on the front-end. For intensive operations, you might want to adjust these intervals or, more importantly, implement logic to *throttle* your Heartbeat requests. You can control the Heartbeat interval using the heartbeat_settings filter.
/**
* Adjust Heartbeat settings.
*
* @param array $settings The current Heartbeat settings.
* @return array The modified Heartbeat settings.
*/
function my_custom_heartbeat_settings( $settings ) {
// Example: Increase interval to 2 minutes (120 seconds)
$settings['interval'] = 120;
// Example: Disable Heartbeat on specific pages or contexts if not needed.
// if ( is_admin() && isset( $_GET['page'] ) && $_GET['page'] === 'my-plugin-settings' ) {
// $settings['disabled'] = true;
// }
return $settings;
}
add_filter( 'heartbeat_settings', 'my_custom_heartbeat_settings' );
Be mindful of the server load generated by frequent Heartbeat requests, especially if your handlers perform complex database queries or external API calls. Consider using transients or caching to optimize data retrieval.
Security and Data Validation
Always validate and sanitize any data received from the client via Heartbeat, just as you would with any other AJAX request. Use nonces for critical operations if necessary, although Heartbeat’s inherent AJAX nature and user context provide a baseline level of security. Ensure that sensitive data is not exposed unnecessarily.
Front-End Heartbeat Initialization
By default, Heartbeat is primarily active in the WordPress admin area. To use it on the front-end, you need to enqueue the Heartbeat script and potentially initiate the connection manually. This is typically done within a theme’s functions.php or a plugin’s main file.
/**
* Enqueue Heartbeat script and custom JS for front-end.
*/
function my_enqueue_frontend_heartbeat() {
// Enqueue the WordPress Heartbeat script
wp_enqueue_script( 'heartbeat' );
// Enqueue your custom JavaScript file that handles Heartbeat events
wp_enqueue_script(
'my-custom-heartbeat-handler',
get_template_directory_uri() . '/js/heartbeat-handler.js', // Adjust path as needed
array( 'jquery', 'heartbeat' ), // Dependencies
filemtime( get_template_directory() . '/js/heartbeat-handler.js' ), // Version based on file modification
true // Load in footer
);
// Pass localized data if needed (e.g., AJAX URL, nonces)
wp_localize_script( 'my-custom-heartbeat-handler', 'myHeartbeatConfig', array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
// Add any other configuration variables
) );
// IMPORTANT: For Heartbeat to connect on the front-end, you might need to
// ensure the 'heartbeat_settings' filter is not disabling it, and
// potentially call wp.heartbeat.connect() in your JS if it doesn't auto-connect.
// The presence of wp.heartbeat.connect() call in JS is often sufficient.
}
// Only enqueue on specific front-end pages where you need Heartbeat
// Example: Add this to a page template or a conditional check.
// add_action( 'wp_enqueue_scripts', 'my_enqueue_frontend_heartbeat' );
Ensure your heartbeat-handler.js file contains the JavaScript logic described earlier, including the `wp.heartbeat.connect()` call if automatic connection isn’t guaranteed.
Conclusion
By integrating the Heartbeat API with Timber and Twig, developers can create highly dynamic and responsive WordPress experiences. The ability to push real-time data to the client and trigger server-side actions based on client pings opens up a realm of possibilities for custom dashboards, live notifications, and interactive content management. Remember to prioritize performance, security, and clear data schemas to build robust and maintainable extensions.