How to build a real-time feature in WordPress using WebSockets and server-sent events?

Tackling real-time features in WordPress isn’t as scary as it sounds, and the short answer is: you primarily do it with a combination of WebSockets and Server-Sent Events (SSE), often supported by a separate backend service that handles the real-time communication. WordPress itself isn’t built for real-time out-of-the-box, but you can integrate these technologies to add dynamic, responsive experiences to your site. This article will walk you through the practical steps, what you need to consider, and how to make these powerful tools work with your WordPress setup.

In today’s fast-paced digital world, static content often falls short. Users expect instant updates, live notifications, and dynamic interactions. For WordPress sites, this translates into a more engaging experience, whether it’s for a live chat, a breaking news feed, or collaborative editing.

The Limitations of Traditional WordPress

WordPress, at its core, relies heavily on a request-response model. When you load a page, your browser sends a request to the server, the server processes it and sends back the HTML, CSS, and JavaScript. For new information, you typically need to refresh the page or rely on AJAX polling, which can be inefficient and resource-intensive as it consistently asks for updates, even when none are available.

Unlocking New User Experiences

Real-time capabilities open up a host of possibilities for WordPress sites. Imagine:

  • Live comments: See new comments appear instantly without refreshing.
  • Notifications: Get immediate alerts for new messages, updates, or mentions.
  • Collaborative editing: Multiple users working on the same post or document simultaneously.
  • Live dashboards: Real-time analytics or performance monitoring for logged-in users.
  • Chat applications: Direct messaging and live support within your WordPress site.

These features significantly improve user engagement and can differentiate your site from competitors.

If you’re interested in enhancing your WordPress site with real-time features using WebSockets and server-sent events, you might also find it helpful to explore related topics such as server management and migration. A great resource for this is the article on migrating between CyberPanel servers, which can provide insights into maintaining your server environment during such enhancements. You can read more about it here: Migrating to Another Server with CyberPanel.

Understanding WebSockets vs. Server-Sent Events

Before diving into implementation, it’s crucial to understand the two main contenders for real-time communication: WebSockets and Server-Sent Events (SSE). They serve slightly different purposes and have distinct advantages.

WebSockets: Bi-directional Communication Powerhouse

WebSockets provide a persistent, bi-directional communication channel between a client (your user’s browser) and a server. Once established, this connection stays open, allowing both the client and server to send data to each other at any time, without the overhead of repeated HTTP requests.

When to Use WebSockets

WebSockets are ideal for scenarios requiring frequent, low-latency, and bi-directional communication. Think:

  • Chat applications: Users send and receive messages.
  • Multiplayer games: Real-time updates on player movements and actions.
  • Collaborative tools: Live updates as multiple users edit a document.
  • Real-time dashboards: Where users might trigger actions and expect immediate feedback.

How WebSockets Work (Simplified)

  1. Handshake: The client sends an HTTP request to the server, requesting to upgrade the connection to a WebSocket.
  2. Upgrade: If the server supports WebSockets, it responds with an “101 Switching Protocols” status.
  3. Persistent Connection: A dedicated, single TCP connection is established and remains open.
  4. Full-Duplex: Both client and server can send and receive messages simultaneously through this open connection.

Server-Sent Events (SSE): Efficient One-Way Streams

Server-Sent Events allow the server to push updates to the client in a continuous stream over a single, long-lived HTTP connection. Unlike WebSockets, SSE is uni-directional – data flows only from the server to the client.

When to Use SSE

SSE is perfect for situations where you primarily need to send real-time updates from the server to the client, and bi-directional communication isn’t a primary requirement. Good use cases include:

  • Notifications: Alerting users to new activities or messages.
  • Live news feeds: Streaming breaking news headlines.
  • Stock tickers: Continuous updates of stock prices.
  • Progress updates: Showing the real-time status of a long-running process.

How SSE Works (Simplified)

  1. Client Request: The client makes an HTTP GET request to a specific endpoint, often with the Accept: text/event-stream header.
  2. Server Response: The server responds with Content-Type: text/event-stream and keeps the connection open.
  3. Event Data Stream: The server then sends event data formatted in a simple, standardized way.
  4. Automatic Reconnection: Browsers automatically handle reconnection if the connection drops.

Choosing Between WebSockets and SSE

  • **Need to send data from the client frequently?** Go with WebSockets.
  • **Only need to receive updates from the server?** SSE is simpler and often more efficient.
  • Complexity: SSE is generally easier to implement on the client side with the built-in EventSource API. WebSockets usually require a more involved client-side library.
  • Server Burden: While both involve persistent connections, WebSockets can have slightly more overhead due to their bi-directional nature.

For many common WordPress real-time features like notifications or live feeds, SSE is often a simpler and perfectly adequate solution. For interactive chats or collaborative tools, WebSockets are the way to go.

Architectural Considerations for Real-time in WordPress

Integrating real-time capabilities into WordPress isn’t just about dropping in some code. It requires thinking about your overall architecture because WordPress wasn’t designed for this natively.

The Role of a Separate Real-time Server

This is perhaps the most crucial point: Do not try to run WebSockets or SSE directly within the core WordPress PHP execution model. PHP is not truly asynchronous in a way that efficiently handles persistent connections. Each PHP request typically terminates after serving its purpose.

Instead, you’ll need a separate dedicated real-time server. This server will handle the persistent connections (WebSockets or SSE) and communicate with your WordPress database or API when needed. This approach decouples your real-time logic from your WordPress application, leading to better performance and scalability.

Common Real-time Server Technologies

  • Node.js with Socket.IO or ws: Extremely popular for WebSockets due to its asynchronous nature and vast ecosystem.
  • Go with Gorilla WebSocket: Known for its performance and concurrency.
  • Python with asyncio and websockets: A good option if you’re already familiar with Python.
  • PHP with ReactPHP or Swoole: While less common for dedicated real-time servers, these frameworks allow PHP to operate asynchronously, making it possible to build real-time services in PHP itself.

Communication Between WordPress and the Real-time Server

Once you have a separate real-time server, you need a way for your WordPress site to tell that server when something real-time needs to happen.

REST API or Webhooks

When an event occurs in WordPress that should trigger a real-time update (e.g., a new comment is posted, a user likes something), your WordPress site can send a request to your real-time server.

  • WordPress to Real-time Server: Your WordPress site (usually via a custom plugin or theme function) makes an HTTP POST request to an endpoint on your real-time server. This request would contain the event data (e.g., event_type: 'new_comment', comment_id: 123).
  • Real-time Server Action: The real-time server receives this data and then broadcasts it to all relevant connected clients via their open WebSocket or SSE connections.

Message Queues (for Scale)

For larger applications or higher loads, using a message queue like Redis Pub/Sub, RabbitMQ, or Apache Kafka can be highly beneficial.

  • WordPress Publishes: When an event occurs, WordPress publishes a message to a specific topic or channel in the message queue.
  • Real-time Server Subscribes: Your real-time server is subscribed to that same topic/channel.
  • Broadcast: When the real-time server receives a message from the queue, it processes it and broadcasts the relevant update to its connected clients.

This decouples the event generation from the real-time broadcasting, making your system more robust and scalable. If your real-time server goes down momentarily, events queued by WordPress won’t be lost.

Data Persistence and WordPress Database

Your real-time server shouldn’t independently manage core application data. The single source of truth for your WordPress content and user data should remain the WordPress database.

  • Notifications, Live Comments: When a new comment is posted, it’s saved to the WordPress database first. Only then does WordPress notify the real-time server to broadcast the existence of that new comment, perhaps including its ID or a snippet. The client might then fetch the full comment content via a standard AJAX request to WordPress or rely on the real-time server to push sufficient data.
  • Chat Messages: For chat, you’d typically save messages to a custom database table within WordPress (or even a separate database accessible by WordPress) for persistence. The real-time server acts as the conduit for instant delivery but doesn’t necessarily store messages long-term.

Consistency is key. Avoid duplicated data or conflicting states by ensuring WordPress remains the primary data manager.

Setting Up Your Real-time Backend (Node.js Example)

Let’s walk through a simplified example using Node.js and Socket.IO for a WebSocket-based backend. This will handle the persistent connections and message broadcasting.

Prerequisites

  • Node.js and npm installed on your server (can be the same server as WordPress, but a different port, or a completely separate server).
  • Basic understanding of JavaScript.

Step 1: Initialize Your Node.js Project

Create a new directory for your real-time server, navigate into it, and initialize a Node.js project:

“`bash

mkdir wordpress-realtime-server

cd wordpress-realtime-server

npm init -y

“`

Step 2: Install Socket.IO

Socket.IO makes WebSocket implementation much easier.

“`bash

npm install express socket.io cors dotenv

“`

  • express: A web application framework for Node.js.
  • socket.io: The WebSocket library.
  • cors: To handle Cross-Origin Resource Sharing.
  • dotenv: To manage environment variables.

Step 3: Create Your Server File (server.js)

“`javascript

// server.js

require(‘dotenv’).config(); // Load environment variables from .env file

const express = require(‘express’);

const app = express();

const http = require(‘http’);

const server = http.createServer(app);

const { Server } = require(‘socket.io’);

const cors = require(‘cors’);

const PORT = process.env.PORT || 3000; // Use port from .env or default to 3000

const WORDPRESS_ORIGIN = process.env.WORDPRESS_ORIGIN || ‘http://localhost:8000’; // Your WordPress URL

// Configure CORS for Socket.IO

const io = new Server(server, {

cors: {

origin: WORDPRESS_ORIGIN, // Allow requests from your WordPress site

methods: [“GET”, “POST”]

}

});

// Configure CORS for Express (for the webhook endpoint)

app.use(cors({

origin: WORDPRESS_ORIGIN,

methods: [“GET”, “POST”]

}));

app.use(express.json()); // Enable JSON body parsing for Express

// Basic route to check if server is running

app.get(‘/’, (req, res) => {

res.send(‘WordPress Real-time Server is running!’);

});

// Socket.IO Real-time Logic

io.on(‘connection’, (socket) => {

console.log(‘A user connected:’, socket.id);

// Example: Join a room (e.g., for specific post updates)

socket.on(‘joinRoom’, (room) => {

socket.join(room);

console.log(User ${socket.id} joined room: ${room});

});

// Example: Client sending a message (e.g., chat)

socket.on(‘chatMessage’, (msg) => {

console.log(Message from ${socket.id}: ${msg});

// Broadcast to all clients in the same room, or all if no room specified

io.emit(‘newChatMessage’, { from: socket.id, message: msg, timestamp: new Date() });

// Or, if broadcasting to a specific room:

// io.to(‘post_123’).emit(‘newChatMessage’, { from: socket.id, message: msg });

});

socket.on(‘disconnect’, () => {

console.log(‘User disconnected:’, socket.id);

});

});

// Webhook Endpoint from WordPress

app.post(‘/webhook/new-comment’, (req, res) => {

const commentData = req.body;

console.log(‘Received new comment webhook:’, commentData);

// Assuming commentData contains a post_id

const postId = commentData.post_id;

if (postId) {

// Emit event to all clients in the room for this post

io.to(post_${postId}).emit(‘newComment’, {

commentId: commentData.comment_id,

author: commentData.comment_author,

content: commentData.comment_content,

postId: postId,

// … other relevant data

});

console.log(Emitted 'newComment' to room 'post_${postId}');

} else {

// Optionally emit to all clients if not tied to a specific post

io.emit(‘siteWideNotification’, { message: ‘A new comment was posted!’ });

console.log(‘Emitted siteWideNotification for new comment.’);

}

res.status(200).send(‘Webhook received and processed.’);

});

// Start the server

server.listen(PORT, () => {

console.log(Socket.IO Server listening on port ${PORT});

});

“`

Step 4: Create a .env file

In the root of your wordpress-realtime-server directory, create a file named .env:

“`

PORT=3000

WORDPRESS_ORIGIN=http://your-wordpress-domain.com # IMPORTANT: Replace with your actual WordPress domain

“`

Step 5: Run Your Server

“`bash

node server.js

“`

You should see output like: Socket.IO Server listening on port 3000.

If you’re interested in enhancing your WordPress site with real-time features, you might find it beneficial to explore a related article that delves into the intricacies of using WebSockets and server-sent events. This resource provides valuable insights and practical examples that can help you implement these technologies effectively. For more information on optimizing your site’s performance and user experience, check out this helpful guide that complements the topic of real-time features in WordPress.

Integrating with WordPress (Client and Server-side)

Now that your real-time server is running, you need to connect your WordPress site to it. This involves both client-side JavaScript and server-side PHP.

Client-Side Integration: Connecting from the Browser

This part involves adding JavaScript to your WordPress theme or a custom plugin to establish the WebSocket or SSE connection.

For WebSockets (Socket.IO Example)

Add this JavaScript code to your theme’s functions.php (using wp_enqueue_script) or directly in a template file (for development/testing) or a custom plugin.

“`javascript

// Enqueue this script in your WordPress theme or plugin

// assets/js/realtime-client.js

document.addEventListener(‘DOMContentLoaded’, () => {

const socket = io(‘http://your-realtime-server-ip:3000’); // Replace with your Node.js server address and port

socket.on(‘connect’, () => {

console.log(‘Connected to real-time server:’, socket.id);

// Example: If on a single post page, join a room for that post

if (typeof wp_realtime_vars !== ‘undefined’ && wp_realtime_vars.postId) {

socket.emit(‘joinRoom’, post_${wp_realtime_vars.postId});

console.log(Joined room: post_${wp_realtime_vars.postId});

}

});

socket.on(‘newComment’, (data) => {

console.log(‘New comment received:’, data);

// Here, you’d update your DOM to display the new comment.

// For security and best practice, you might only receive comment_id

// and then fetch the actual comment content via AJAX from WordPress.

const commentsList = document.querySelector(‘#comments ol.commentlist’);

if (commentsList) {

// Example: Append a simple temporary message

const newCommentHtml = `

  • ${data.author} says:

    ${data.content}

  • `;

    // Add to the top of the list or bottom, depending on your design

    commentsList.insertAdjacentHTML(‘afterbegin’, newCommentHtml);

    alert(New comment by ${data.author}: "${data.content.substring(0, 50)}..."); // For immediate visual feedback

    }

    });

    socket.on(‘siteWideNotification’, (data) => {

    console.log(‘Site-wide notification:’, data.message);

    // Display a small, non-intrusive notification banner

    const notificationArea = document.getElementById(‘realtime-notifications’);

    if (notificationArea) {

    notificationArea.innerHTML =

    ${data.message}

    + notificationArea.innerHTML;

    setTimeout(() => {

    const firstAlert = notificationArea.querySelector(‘.realtime-alert’);

    if (firstAlert) firstAlert.remove();

    }, 5000); // Remove after 5 seconds

    } else {

    console.log(‘No #realtime-notifications element found to display.’, data.message);

    }

    });

    socket.on(‘disconnect’, () => {

    console.log(‘Disconnected from real-time server’);

    });

    socket.on(‘error’, (error) => {

    console.error(‘Socket.IO error:’, error);

    });

    });

    “`

    You’ll need the Socket.IO client library. You can include it via a CDN or self-host it:

    “`html

    “`

    Enqueueing in WordPress (functions.php)

    “`php

    function enqueue_realtime_scripts() {

    // Enqueue Socket.IO client library

    wp_enqueue_script(

    ‘socket-io-client’,

    ‘https://cdn.socket.io/4.7.5/socket.io.min.js’,

    array(),

    ‘4.7.5’,

    true // In footer

    );

    // Enqueue your custom real-time client script

    wp_enqueue_script(

    ‘my-realtime-client’,

    get_stylesheet_directory_uri() . ‘/assets/js/realtime-client.js’, // Adjust path

    array(‘socket-io-client’), // Depends on socket-io-client

    ‘1.0.0’,

    true // In footer

    );

    // Pass dynamic variables to your script

    if ( is_single() ) { // Only on single post pages

    wp_localize_script(

    ‘my-realtime-client’,

    ‘wp_realtime_vars’,

    array(

    ‘postId’ => get_the_ID(),

    // ‘ajaxUrl’ => admin_url(‘admin-ajax.php’), // If you need AJAX

    )

    );

    }

    }

    add_action(‘wp_enqueue_scripts’, ‘enqueue_realtime_scripts’);

    “`

    And remember to add a placeholder element in your theme for notifications (e.g., in header.php or footer.php):

    For Server-Sent Events (SSE Example)

    “`javascript

    // assets/js/sse-client.js

    document.addEventListener(‘DOMContentLoaded’, () => {

    // Only connect if the browser supports EventSource

    if (typeof EventSource !== ‘undefined’) {

    const eventSource = new EventSource(‘http://your-realtime-server-ip:3000/events/new-comments’); // Your SSE endpoint

    eventSource.onopen = function() {

    console.log(‘SSE connection opened.’);

    };

    eventSource.onmessage = function(event) {

    // This catches any message that doesn’t have an ‘event’ type

    console.log(‘Generic SSE message:’, event.data);

    };

    eventSource.addEventListener(‘newComment’, function(event) {

    const data = JSON.parse(event.data);

    console.log(‘New comment via SSE:’, data);

    // Same DOM update logic as with WebSockets

    const commentsList = document.querySelector(‘#comments ol.commentlist’);

    if (commentsList) {

    if (typeof wp_realtime_vars !== ‘undefined’ && wp_realtime_vars.postId == data.postId) {

    const newCommentHtml = ... (same as above) ...;

    commentsList.insertAdjacentHTML(‘afterbegin’, newCommentHtml);

    alert(New comment by ${data.author}: "${data.content.substring(0, 50)}...");

    }

    }

    });

    eventSource.onerror = function(error) {

    console.error(‘SSE Error:’, error);

    eventSource.close(); // Close on error and let browser auto-reconnect or handle manually

    };

    // You might want to close the connection when the user navigates away

    window.addEventListener(‘beforeunload’, () => {

    eventSource.close();

    });

    } else {

    console.warn(‘Your browser does not support Server-Sent Events. Real-time features will not work.’);

    }

    });

    “`

    The Node.js server would need a corresponding SSE endpoint:

    “`javascript

    // server.js (add this for SSE)

    app.get(‘/events/new-comments’, (req, res) => {

    res.writeHead(200, {

    ‘Content-Type’: ‘text/event-stream’,

    ‘Cache-Control’: ‘no-cache’,

    ‘Connection’: ‘keep-alive’,

    ‘Access-Control-Allow-Origin’: WORDPRESS_ORIGIN // Crucial for CORS

    });

    const sendEvent = (data) => {

    res.write(event: newComment\n);

    res.write(data: ${JSON.stringify(data)}\n\n); // Two newlines to signify end of event

    };

    // Store the client connection to be able to send events later

    // In a real app, you’d manage multiple clients and perhaps rooms.

    // For simplicity here, we’re just showing how to send.

    const clientId = Date.now();

    console.log(SSE client ${clientId} connected.);

    // You would typically have a mechanism to fetch and send updates

    // from your message queue (e.g., Redis Pub/Sub) or direct API calls

    // when a WordPress event happens.

    // For illustrative purposes, let’s simulate sending an event after a delay

    // In reality, this would be triggered by a webhook from WordPress.

    // let count = 0;

    // const interval = setInterval(() => {

    // count++;

    // sendEvent({

    // message: Simulated update ${count},

    // timestamp: new Date()

    // });

    // }, 5000);

    req.on(‘close’, () => {

    console.log(SSE client ${clientId} disconnected.);

    // clearInterval(interval); // Clean up any associated intervals

    res.end();

    });

    });

    “`

    Server-Side Integration (WordPress PHP)

    This involves telling your Node.js real-time server when certain events happen in WordPress. We’ll use the wp_insert_comment hook as an example to trigger a webhook.

    Add this PHP code to your custom plugin or your theme’s functions.php:

    “`php

    // functions.php or custom plugin file

    function my_realtime_new_comment_notification( $comment_id, $comment_approved, $commentdata ) {

    // Only send notification if the comment is approved (or your desired status)

    if ( 1 === $comment_approved ) {

    $realtime_server_webhook_url = ‘http://your-realtime-server-ip:3000/webhook/new-comment’; // Replace with your Node.js server URL

    $webhook_payload = array(

    ‘comment_id’ => $comment_id,

    ‘comment_author’ => $commentdata[‘comment_author’],

    ‘comment_content’ => $commentdata[‘comment_content’],

    ‘post_id’ => $commentdata[‘comment_post_ID’],

    ‘comment_date’ => $commentdata[‘comment_date’],

    // Add any other relevant data you want to send

    );

    $response = wp_safe_remote_post(

    $realtime_server_webhook_url,

    array(

    ‘method’ => ‘POST’,

    ‘timeout’ => 15,

    ‘headers’ => array(

    ‘Content-Type’ => ‘application/json’,

    ),

    ‘body’ => json_encode( $webhook_payload ),

    ‘sslverify’ => false, // Set to true in production with proper SSL certs

    ‘blocking’ => false, // Don’t wait for response from webhook (async call)

    )

    );

    if ( is_wp_error( $response ) ) {

    error_log( ‘Real-time webhook error: ‘ . $response->get_error_message() );

    } else {

    // Uncomment next line for debugging if blocking is true

    // error_log( ‘Real-time webhook sent. Response: ‘ . wp_remote_retrieve_body( $response ) );

    }

    }

    }

    add_action( ‘wp_insert_comment’, ‘my_realtime_new_comment_notification’, 10, 3 );

    “`

    Explanation for wp_safe_remote_post:

    • 'blocking' => false: This is crucial for performance. It tells WordPress not to wait for a response from your real-time server. The webhook request will be sent in the background, allowing your WordPress comment creation process to complete quickly. If you set this to true, a slow real-time server could cause your comment submissions to hang.
    • 'sslverify' => false: Only use false in development if your Node.js server doesn’t have a valid SSL certificate. Always set this to true in production and ensure your real-time server endpoint uses https:// with a valid certificate.

    This setup ensures that whenever a new comment is approved in WordPress, a notification is sent to your real-time server, which then broadcasts it to all connected clients.

    Security, Scalability, and Deployment

    Building real-time features involves challenges beyond just getting them to work.

    Security Considerations

    • CORS: Ensure your real-time server correctly configures Cross-Origin Resource Sharing (CORS) to only allow connections from your trusted WordPress domain.
    • Authentication/Authorization: If your real-time features are for logged-in users or specific roles, you need to implement authentication. When a client connects to your WebSocket/SSE server, they should send some form of token (e.g., JWT) that your server can validate against your WordPress user system (or a shared user database). Otherwise, anyone could connect and potentially spam or impersonate users.
    • Input Validation: Always validate and sanitize any data received from clients before processing or broadcasting to prevent cross-site scripting (XSS) or other injection attacks.
    • Rate Limiting: Protect your real-time server (and WordPress webhook endpoint) from abuse by implementing rate limiting on message sending or connection attempts.

    Scalability

    • Horizontal Scaling: For high traffic, you’ll likely need multiple instances of your real-time server. A message queue (like Redis Pub/Sub) becomes essential here. Each real-time server instance would subscribe to the queue, and when WordPress (or another service) publishes an event, all instances receive it and can broadcast to their connected clients.
    • Load Balancers: Place a load balancer in front of your real-time server instances to distribute incoming connections. For WebSockets, this requires a load balancer that supports sticky sessions (maintaining a client’s connection to the same server instance).
    • Database Interactions: Minimize direct database lookups from your real-time server. Instead, push enough data in the real-time event, or have clients fetch details via WordPress’s REST API if needed.

    Deployment

    • Separate Hosting: Consider hosting your real-time server on a dedicated VPS, a container service (Docker, Kubernetes), or a serverless platform (AWS Lambda, Google Cloud Functions) rather than on the same shared host as your WordPress site. This provides better resource isolation and scalability.
    • Process Manager: Use a process manager like PM2 (for Node.js) to keep your real-time server running reliably, automatically restarting it if it crashes.
    • Reverse Proxy: Use a reverse proxy like Nginx or Apache in front of your real-time server. This allows you to:
    • Proxy requests to your Node.js server on a standard port (e.g., 80 or 443).
    • Handle SSL termination (HTTPS).
    • Enable WebSocket proxying (crucial for Socket.IO).
    • Add caching layers or additional security.

    Monitoring and Logging

    Real-time systems can be complex. Implement robust logging on both your WordPress and real-time servers to debug issues. Monitor connection counts, message rates, and server resource usage (CPU, memory).

    Conclusion

    Adding real-time features to your WordPress site significantly boosts user engagement and unlocks modern web experiences. While WordPress itself isn’t designed for persistent connections, by leveraging external real-time servers with technologies like WebSockets (e.g., Socket.IO) or Server-Sent Events (SSE), you can effectively integrate these capabilities.

    The key takeaway is to decouple your real-time backend from WordPress’s PHP processes. Use WordPress to manage content and trigger events, and let a dedicated real-time server handle the persistent connections and message broadcasting. With careful planning for architecture, security, and scalability, you can transform your static WordPress site into a dynamic, interactive platform. It’s a journey that adds complexity but delivers immense value to your users.