Why publish realtime not working as expected

I am not able to send to receiver_id user. message_details is a table field
import frappe
from frappe.model.document import Document

class CandidatesChatMessage(Document):
def after_insert(self):
# Check if the message_details child table has any rows
if self.message_details:
for row in self.message_details:
if row.receiver_id:
frappe.publish_realtime(
event=“candidate_receive_message”,
message={
“sender_id”: row.sender_id,
“message”: row.message
},
user=row.receiver_id, # Send event to this receiver,
after_commit=True
)

there are a couple of common pitfalls with realtime events in ERPNext/Frappe that match your code.

Here’s the breakdown:


1. user in publish_realtime must be a valid system user ID

In Frappe, the user parameter must exactly match the User.name (usually the email like test@example.com), not a custom field like an employee ID, candidate ID, or numeric PK.

If your receiver_id is coming from message_details and is not the actual User.name from tabUser, the message will never reach the target.

Fix:
Before calling publish_realtime, make sure row.receiver_id is the actual frappe.get_doc("User", ...) .name.

Example:

python

user_id = frappe.db.get_value("User", {"custom_id_field": row.receiver_id}, "name")
if user_id:
    frappe.publish_realtime(
        event="candidate_receive_message",
        message={
            "sender_id": row.sender_id,
            "message": row.message
        },
        user=user_id,
        after_commit=True
    )

2. Event listening in JS must match

Your frontend must be listening to candidate_receive_message for that logged-in user.

Example:

javascript

frappe.realtime.on("candidate_receive_message", function(data) {
    console.log("Message received:", data);
});

And make sure the socket is connected — if using a desk page, it’s automatic; for website context, you need to enable it.


3. after_commit=True requires transaction commit

Since you are using after_insert (which happens before the DB commit), after_commit=True is correct — but the actual push will only happen after the database transaction finishes.
If you’re testing in a script runner or in a failed transaction, the realtime event won’t trigger.


4. Possible mismatch with child table values

If your message_details table is empty or receiver_id is None at after_insert, nothing will fire. In many cases, child tables aren’t yet populated at after_insert if they’re added after save in UI scripts.

To confirm, add:

python

frappe.logger().info(f"Message details: {self.message_details}")

:white_check_mark: Minimal working version:

python

import frappe
from frappe.model.document import Document

class CandidatesChatMessage(Document):
    def after_insert(self):
        if self.message_details:
            for row in self.message_details:
                if row.receiver_id:
                    # Make sure receiver_id is an actual frappe User.name
                    user_id = frappe.db.get_value("User", row.receiver_id, "name") \
                               or row.receiver_id
                    frappe.publish_realtime(
                        event="candidate_receive_message",
                        message={
                            "sender_id": row.sender_id,
                            "message": row.message
                        },
                        user=user_id,
                        after_commit=True
                    )

receiver_id value is user@example.com
Doing same steps as your still not working. Before too i tried with realtime.on

If your frappe.publish_realtime works for general broadcasts but not when specifying user=row.receiver_id, there are a few common gotchas in ERPNext/Frappe that you should check:


:one: The user parameter expects a Frappe system user ID

  • If receiver_id is user@example.com, ensure:
    • That exact string exists in User → Email in ERPNext.
    • The user is enabled and has logged in at least once (so the WebSocket session is active).
  • If you have a custom field storing a user’s email, make sure it’s exactly the same as frappe.session.user for that account.

:two: WebSocket connection scope

publish_realtime sends messages only to connected WebSocket sessions.

  • If the user is not currently logged in or not on a page that subscribes to that event, they won’t receive it.
  • Test by opening two browser tabs:
    • Tab 1 → logged in as sender.
    • Tab 2 → logged in as receiver_id.
    • Trigger the code in Tab 1, check console in Tab 2.

:three: Client-side listener

Your frontend must subscribe to the same event name with the correct handler:

javascript

frappe.realtime.on("candidate_receive_message", function (data) {
    console.log("Message received:", data);
    frappe.utils.play_sound("email");
});

:four: After commit timing

Since you’re using after_commit=True, the event will fire after the DB transaction is committed.

  • If you’re testing in after_insert, this should work fine — but try without after_commit=True to rule out transaction issues.

:five: Using message_details table

If message_details is a child table, make sure the loop actually runs and row.receiver_id is not empty or different than expected.

python

for row in self.message_details:
    frappe.logger().debug(f"Publishing to: {row.receiver_id}")

:six: Alternative approach for testing

If direct user targeting fails, test a broadcast to verify if the WebSocket link works at all:

python

frappe.publish_realtime(
    event="candidate_receive_message",
    message={"test": "Hello world"}
)

If broadcast works but user= fails, the issue is specifically with the matching of row.receiver_id to an active logged-in system user.

broadcast too not working in console seeing this error
Error connecting to socket.io: Unauthorized: Error: getaddrinfo ENOTFOUND connecting.localhost

basically means your browser is trying to connect to connecting.localhost, which is not a valid hostname.
It’s not an ERPNext-specific error — it’s coming from the socket.io client because the socket URL is wrong.


Why this happens in ERPNext

ERPNext’s realtime events (e.g., frappe.realtime.publish / frappe.realtime.on) use a WebSocket connection to the site’s URL.
If site_config.json or your host_name is wrong, or if you’re running from a different origin (like a tunnel or VM), the client might build an incorrect URL like connecting.localhost instead of localhost or your actual domain.


Checklist to fix it

  1. Check site_config.json
    Inside your site folder (sites/[yoursite]/site_config.json), make sure you have:

json

{
  "db_name": "...",
  "db_password": "...",
  "host_name": "http://localhost:8000"
}

Or replace localhost with your real domain if accessed externally.
2. Check frappe.utils.get_host_name() value
In bench console:

python

import frappe
frappe.utils.get_host_name()

If it returns something wrong like connecting.localhost, that’s your root cause — fix the host_name in site_config.json.
3. Clear cache after change

bash

bench clear-cache
bench restart
  1. Set correct socket URL in JS if needed
    If you’re testing manually in browser console and connecting to socket.io yourself, use:

javascript

const socket = io("http://localhost:8000", {
    withCredentials: true,
    transports: ["websocket"]
});
  1. If using HTTPS or external host
  • Use the actual domain, e.g., https://test.yourdomain.com
  • Make sure the port matches your frappe-worker / socketio process

socket is connected still not working

@management
I’m having the same issue, the problem that i found in my case was the following
I’m developing my site in react with vite, and using vite proxy to the server, for example from localhost port 3000 to 8000
And the socket io server, validates the origin and the host to match, and even if you try to change the origin header, it won’t work.
I haven’t figure it out

You need to connect to socketio as in new versions of frappe socket is not connected by defaulted. I connected to socketio in bench then it got resolved

not sure what you mean with connect to socketio
I have running
web frappe on :8000
socketio frappe on :9000

and i have a react vite project in port 3000, with a proxy to the 8000 to avoid cors errors