🌍 Global Mirror β€” Visit original CN site β†’
Skip to main content

πŸ”” Events: Using __event_emitter__ and __event_call__ in Open WebUI

Open WebUI's plugin architecture is not just about processing input and producing outputβ€”it's about real-time, interactive communication with the UI and users. To make your Tools, Functions, and Pipes more dynamic, Open WebUI provides a built-in event system via the __event_emitter__ and __event_call__ helpers.

This guide explains what events are, how you can trigger them from your code, and the full catalog of event types you can use (including much more than just "input").


🌊 What Are Events?​

Events are real-time notifications or interactive requests sent from your backend code (Tool, or Function) to the web UI. They allow you to update the chat, display notifications, request confirmation, run UI flows, and more.

  • Events are sent using the __event_emitter__ helper for one-way updates, or __event_call__ when you need user input or a response (e.g., confirmation, input, etc.).

Metaphor: Think of Events like push notifications and modal dialogs that your plugin can trigger, making the chat experience richer and more interactive.


🏁 Availability​

Native Python Tools & Functions​

Events are fully available for native Python Tools and Functions defined directly in Open WebUI using the __event_emitter__ and __event_call__ helpers.

External Tools (OpenAPI & MCP)​

External tools can emit events via a dedicated REST endpoint. Open WebUI passes the following headers to all external tool requests when ENABLE_FORWARD_USER_INFO_HEADERS=True is set:

HeaderDescription
X-Open-WebUI-Chat-IdThe chat ID where the tool was invoked
X-Open-WebUI-Message-IdThe message ID associated with the tool call

Your external tool can use these headers to emit events back to the UI via:

POST /api/v1/chats/{chat_id}/messages/{message_id}/event

See External Tool Events below for details.


🧰 Basic Usage​

Sending an Event​

You can trigger an event anywhere inside your Tool, or Function by calling:

await __event_emitter__(
{
"type": "status", # See the event types list below
"data": {
"description": "Processing started!",
"done": False,
"hidden": False,
},
}
)

You do not need to manually add fields like chat_id or message_idβ€”these are handled automatically by Open WebUI.

Interactive Events​

When you need to pause execution until the user responds (e.g., confirm/cancel dialogs, code execution, or input), use __event_call__:

result = await __event_call__(
{
"type": "input", # Or "confirmation", "execute"
"data": {
"title": "Please enter your password",
"message": "Password is required for this action",
"placeholder": "Your password here",
},
}
)

# result will contain the user's input value

πŸ“œ Event Payload Structure​

When you emit or call an event, the basic structure is:

{
"type": "event_type", // See full list below
"data": { ... } // Event-specific payload
}

Most of the time, you only set "type" and "data". Open WebUI fills in the routing automatically.


πŸ—‚ Full List of Event Types​

Below is a comprehensive table of all supported type values for events, along with their intended effect and data structure. (This is based on up-to-date analysis of Open WebUI event handling logic.)

typeWhen to useData payload structure (examples)
statusShow a status update/history for a message{description: ..., done: bool, hidden: bool}
chat:completionProvide a chat completion result(Custom, see Open WebUI internals)
chat:message:delta,
message
Append content to the current message{content: "text to append"}
chat:message,
replace
Replace current message content completely{content: "replacement text"}
chat:message:files,
files
Set or overwrite message files (for uploads, output){files: [...]}
chat:titleSet (or update) the chat conversation titleTopic string OR {title: ...}
chat:tagsUpdate the set of tags for a chatTag array or object
source,
citation
Add a source/citation, or code execution resultFor code: See below.
notificationShow a notification ("toast") in the UI{type: "info" or "success" or "error" or "warning", content: "..."}
confirmation
(needs __event_call__)
Ask for confirmation (OK/Cancel dialog){title: "...", message: "..."}
input
(needs __event_call__)
Request simple user input ("input box" dialog){title: "...", message: "...", placeholder: "...", value: ..., type: "password"} (type is optional)
execute
(needs __event_call__)
Request user-side code execution and return result{code: "...javascript code..."}
chat:message:favoriteUpdate the favorite/pin status of a message{"favorite": bool}

Other/Advanced types:

  • You can define your own types and handle them at the UI layer (or use upcoming event-extension mechanisms).

❗ Details on Specific Event Types​

status​

Show a status/progress update in the UI:

await __event_emitter__(
{
"type": "status",
"data": {
"description": "Step 1/3: Fetching data...",
"done": False,
"hidden": False,
},
}
)

chat:message:delta or message​

Streaming output (append text):

await __event_emitter__(
{
"type": "chat:message:delta", # or simply "message"
"data": {
"content": "Partial text, "
},
}
)

# Later, as you generate more:
await __event_emitter__(
{
"type": "chat:message:delta",
"data": {
"content": "next chunk of response."
},
}
)

chat:message or replace​

Set (or replace) the entire message content:

await __event_emitter__(
{
"type": "chat:message", # or "replace"
"data": {
"content": "Final, complete response."
},
}
)

files or chat:message:files​

Attach or update files:

await __event_emitter__(
{
"type": "files", # or "chat:message:files"
"data": {
"files": [
# Open WebUI File Objects
]
},
}
)

chat:title​

Update the chat's title:

await __event_emitter__(
{
"type": "chat:title",
"data": {
"title": "Market Analysis Bot Session"
},
}
)

chat:tags​

Update the chat's tags:

await __event_emitter__(
{
"type": "chat:tags",
"data": {
"tags": ["finance", "AI", "daily-report"]
},
}
)

source or citation (and code execution)​

Add a reference/citation:

await __event_emitter__(
{
"type": "source", # or "citation"
"data": {
# Open WebUI Source (Citation) Object
}
}
)

For code execution (track execution state):

await __event_emitter__(
{
"type": "source",
"data": {
# Open WebUI Code Source (Citation) Object
}
}
)

notification​

Show a toast notification:

await __event_emitter__(
{
"type": "notification",
"data": {
"type": "info", # "success", "warning", "error"
"content": "The operation completed successfully!"
}
}
)

chat:message:favorite​

Update the favorite/pin status of a message:

await __event_emitter__(
{
"type": "chat:message:favorite",
"data": {
"favorite": True # or False to unpin
}
}
)

What this does exactly: This event forces the Open WebUI frontend to update the "favorite" state of a message in its local cache. Without this emitter, if an Action Function modifies the message.favorite field in the database directly, the frontend (which maintains its own state) might overwrite your change during its next auto-save cycle. This emitter ensures the UI and database stay perfectly in sync.

Where it appears:

  • Message Toolbar: When set to True, the "Heart" icon beneath the message will fill in, indicating it is favorited.
  • Chat Overview: Favorited messages (pins) are highlighted in the conversation overview, making it easier for users to locate key information later.

Example: "Pin Message" Action​

For a practical implementation of this event in a real-world plugin, see the Pin Message Action on Open WebUI Community. This action demonstrates how to toggle the favorite status in the database and immediately sync the UI using the chat:message:favorite event.


confirmation (requires __event_call__)​

Show a confirm dialog and get user response:

result = await __event_call__(
{
"type": "confirmation",
"data": {
"title": "Are you sure?",
"message": "Do you really want to proceed?"
}
}
)

if result: # or check result contents
await __event_emitter__({
"type": "notification",
"data": {"type": "success", "content": "User confirmed operation."}
})
else:
await __event_emitter__({
"type": "notification",
"data": {"type": "warning", "content": "User cancelled."}
})

input (requires __event_call__)​

Prompt user for text input:

result = await __event_call__(
{
"type": "input",
"data": {
"title": "Enter your name",
"message": "We need your name to proceed.",
"placeholder": "Your full name"
}
}
)

user_input = result
await __event_emitter__(
{
"type": "notification",
"data": {"type": "info", "content": f"You entered: {user_input}"}
}
)

Masked / Password Input​

To hide sensitive input (e.g., API keys, passwords), set type to "password" in the data payload. The input field will be rendered as a masked password input with a show/hide toggle:

result = await __event_call__(
{
"type": "input",
"data": {
"title": "Enter API Key",
"message": "Your API key is required for this integration.",
"placeholder": "sk-...",
"type": "password"
}
}
)
tip

This uses the same SensitiveInput component used for user valve password fields, providing a familiar "eye" icon toggle for showing/hiding the value.


execute (requires __event_call__)​

Run JavaScript directly in the user's browser:

result = await __event_call__(
{
"type": "execute",
"data": {
"code": "return document.title;",
}
}
)

await __event_emitter__(
{
"type": "notification",
"data": {
"type": "info",
"content": f"Page title: {result}"
}
}
)

How It Works​

The execute event runs JavaScript directly in the main page context using new Function(). This means:

  • It runs with full access to the page's DOM, cookies, localStorage, and session
  • It is not sandboxed β€” there are no iframe restrictions
  • It can manipulate the Open WebUI interface directly (show/hide elements, read form data, trigger downloads)
  • The code runs as an async function, so you can use await and return a value back to the backend
Frontend Automation

Because execute runs in the main page context with full DOM access, you can use it to automate virtually anything on the Open WebUI frontend: click buttons, fill input fields, navigate between pages, read page state, trigger downloads, interact with the model selector, submit messages on behalf of the user, and more. Think of it as a remote control for the browser UI β€” if a user can do it manually, your function can do it programmatically via execute.

Example: Display a Custom Form​

result = await __event_call__(
{
"type": "execute",
"data": {
"code": """
return new Promise((resolve) => {
const overlay = document.createElement('div');
overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center;z-index:9999';
overlay.innerHTML = `
<div style="background:white;padding:24px;border-radius:12px;min-width:300px">
<h3 style="margin:0 0 12px">Enter Details</h3>
<input id="exec-name" placeholder="Name" style="width:100%;padding:8px;margin:4px 0;border:1px solid #ccc;border-radius:6px"/>
<input id="exec-email" placeholder="Email" style="width:100%;padding:8px;margin:4px 0;border:1px solid #ccc;border-radius:6px"/>
<button id="exec-submit" style="margin-top:12px;padding:8px 16px;background:#333;color:white;border:none;border-radius:6px;cursor:pointer">Submit</button>
</div>
`;
document.body.appendChild(overlay);
document.getElementById('exec-submit').onclick = () => {
const name = document.getElementById('exec-name').value;
const email = document.getElementById('exec-email').value;
overlay.remove();
resolve({ name, email });
};
});
"""
}
}
)
# result will be {"name": "...", "email": "..."}

Execute vs Rich UI Embeds​

The execute event and Rich UI Embeds are complementary ways to create interactive experiences:

execute EventRich UI Embed
Runs inMain page context (no sandbox)Sandboxed iframe
PersistenceEphemeral β€” gone on reload/navigatePersistent β€” saved in chat history
Page accessFull (DOM, cookies, localStorage)Isolated from parent by default
FormsAlways works (no sandbox)Requires allowForms setting enabled
Best forTransient interactions, side effects, downloads, DOM manipulationPersistent visual content, dashboards, charts

Use execute for transient interactions (confirmations, custom dialogs, triggering downloads, reading page state) and Rich UI embeds for persistent visual content you want to stay in the conversation.

warning

Because execute runs unsandboxed JavaScript in the user's browser session, it has full access to the Open WebUI page context. Only use this in trusted functions β€” never execute untrusted or user-provided code through this event.


πŸ—οΈ When & Where to Use Events​

  • From any Tool, or Function in Open WebUI.
  • To stream responses, show progress, request user data, update the UI, or display supplementary info/files.
  • await __event_emitter__ is for one-way messages (fire and forget).
  • await __event_call__ is for when you need a response from the user (input, execute, confirmation).

πŸ’‘ Tips & Advanced Notes​

  • Multiple types per message: You can emit several events of different types for one messageβ€”for example, show status updates, then stream with chat:message:delta, then complete with a chat:message.
  • Custom event types: While the above list is the standard, you may use your own types and detect/handle them in custom UI code.
  • Extensibility: The event system is designed to evolveβ€”always check the Open WebUI documentation for the most current list and advanced usage.

🧐 FAQ​

Q: How do I trigger a notification for the user?​

Use notification type:

await __event_emitter__({
"type": "notification",
"data": {"type": "success", "content": "Task complete"}
})

Q: How do I prompt the user for input and get their answer?​

Use:

response = await __event_call__({
"type": "input",
"data": {
"title": "What's your name?",
"message": "Please enter your preferred name:",
"placeholder": "Name"
}
})

# response will be: {"value": "user's answer"}

Q: What event types are available for __event_call__?​

  • "input": Input box dialog
  • "confirmation": Yes/No, OK/Cancel dialog
  • "execute": Run provided code on client and return result

Q: Can I update files attached to a message?​

Yesβ€”use the "files" or "chat:message:files" event type with a {files: [...]} payload.

Q: Can I update the conversation title or tags?​

Absolutely: use "chat:title" or "chat:tags" accordingly.

Q: Can I stream responses (partial tokens) to the user?​

Yesβ€”emit "chat:message:delta" events in a loop, then finish with "chat:message".


🌐 External Tool Events​

External tools (OpenAPI and MCP servers) can emit events to the Open WebUI UI via a REST endpoint. This enables features like status updates, notifications, and streaming content from tools running on external servers.

Prerequisites​

To receive the chat and message ID headers, you must enable header forwarding by setting the following environment variable on your Open WebUI instance:

ENABLE_FORWARD_USER_INFO_HEADERS=True

Without this, Open WebUI will not include the identification headers in requests to external tools, and event emitting will not work.

Headers Provided by Open WebUI​

When Open WebUI calls your external tool (with header forwarding enabled), it includes these headers:

HeaderDescriptionEnv Var Override
X-Open-WebUI-Chat-IdThe chat ID where the tool was invokedFORWARD_SESSION_INFO_HEADER_CHAT_ID
X-Open-WebUI-Message-IdThe message ID associated with the tool callFORWARD_SESSION_INFO_HEADER_MESSAGE_ID

Event Endpoint​

Endpoint: POST /api/v1/chats/{chat_id}/messages/{message_id}/event

Authentication: Requires a valid Open WebUI API key or session token.

Request Body:

{
"type": "status",
"data": {
"description": "Processing your request...",
"done": false
}
}

Supported Event Types​

External tools can emit the same event types as native tools:

  • status – Show progress/status updates
  • notification – Display toast notifications
  • chat:message:delta / message – Append content to the message
  • chat:message / replace – Replace message content
  • files / chat:message:files – Attach files
  • source / citation – Add citations
note

Interactive events (input, confirmation, execute) require __event_call__ and are not supported for external tools as they need bidirectional WebSocket communication.

Example: Python External Tool​

import httpx

def my_tool_handler(request):
# Extract headers from incoming request
chat_id = request.headers.get("X-Open-WebUI-Chat-Id")
message_id = request.headers.get("X-Open-WebUI-Message-Id")
api_key = "your-open-webui-api-key"

# Emit a status event
httpx.post(
f"http://your-open-webui-host/api/v1/chats/{chat_id}/messages/{message_id}/event",
headers={"Authorization": f"Bearer {api_key}"},
json={
"type": "status",
"data": {"description": "Working on it...", "done": False}
}
)

# ... do work ...

# Emit completion status
httpx.post(
f"http://your-open-webui-host/api/v1/chats/{chat_id}/messages/{message_id}/event",
headers={"Authorization": f"Bearer {api_key}"},
json={
"type": "status",
"data": {"description": "Complete!", "done": True}
}
)

return {"result": "success"}

Example: JavaScript/Node.js External Tool​

async function myToolHandler(req) {
const chatId = req.headers['x-open-webui-chat-id'];
const messageId = req.headers['x-open-webui-message-id'];
const apiKey = 'your-open-webui-api-key';

// Emit a notification
await fetch(
`http://your-open-webui-host/api/v1/chats/${chatId}/messages/${messageId}/event`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'notification',
data: { type: 'info', content: 'Tool is processing...' }
})
}
);

return { result: 'success' };
}

πŸ”’ Persistence & Browser Disconnection​

A common question is: what happens if the browser tab is closed while a tool, action, or pipe is still running?

Server-Side Execution Continues​

When you send a chat request, Open WebUI creates a background asyncio task that is not tied to your HTTP connection or Socket.IO session. If you close the tab:

  1. The WebSocket disconnects and the Socket.IO disconnect handler fires
  2. The disconnect handler cleans up session data but does not cancel any running tasks
  3. The background task continues running to completion on the server
  4. sio.emit() calls succeed silently β€” events are sent to an empty room and discarded
  5. Database writes still happen for persisted event types (see below)
  6. The task runs until the function returns, raises an error, or is manually cancelled
No Execution Timeout

There is no timeout on pipe, tool, or action execution. Your code can run for minutes or hours β€” nothing in Open WebUI will kill it automatically. The only things that can stop a running task are:

  • The function itself returning or raising an exception
  • Manual cancellation via POST /api/tasks/stop/{task_id} (the stop button in the UI)
  • The Open WebUI server process restarting

Which Event Types Are Persisted to the Database?​

The event emitter writes certain event types directly to the database regardless of whether a browser is connected. These writes are independent of the ENABLE_REALTIME_CHAT_SAVE setting.

βœ… Persisted (survive tab close)​

TypeWhat's saved
statusAppended to the message's statusHistory array
messageAppended to the message's content field
replaceOverwrites the message's content field
embedsAppended to the message's embeds array (Rich UI HTML)
filesAppended to the message's files array
source / citationAppended to the message's sources array

These 6 types always write to the database inside the event emitter function itself, completely independent of ENABLE_REALTIME_CHAT_SAVE.

Use Short Names for Persistence

The backend event emitter only recognizes the short names above for DB writes. If you emit "chat:message:embeds" instead of "embeds", the frontend handles it identically, but the backend won't persist it. Always use the short names ("status", "message", "replace", "embeds", "files", "source") if you need persistence.

❌ Not persisted (lost on tab close)​

TypeWhy it's lost
chat:completionStreaming LLM deltas β€” Socket.IO only
chat:message:deltaFrontend alias, backend doesn't persist
chat:messageFrontend alias, backend doesn't persist
chat:message:filesFrontend alias, backend doesn't persist
chat:message:embedsFrontend alias, backend doesn't persist
chat:message:errorSocket.IO only
chat:message:follow_upsSocket.IO only
chat:message:favoriteSocket.IO only (updates frontend state)
chat:titleSocket.IO only
chat:tagsSocket.IO only
notificationToast popup β€” Socket.IO only
Alternative for Streaming LLM Output

If your pipe or tool needs to call an LLM and have the result persist even when the browser is closed, you can import and use generate_chat_completion from Open WebUI's internals instead of emitting chat:completion events. The completion flows through the normal chat pipeline and its result is saved to the database like any other assistant message.

⚠️ Requires live connection (will error on tab close)​

TypeWhy
confirmationUses sio.call() β€” waits for client response, will timeout
inputUses sio.call() β€” waits for client response, will timeout
executeUses sio.call() β€” waits for client response, will timeout

These __event_call__ types fundamentally require a live browser connection. If the tab is closed, sio.call() will timeout and raise an exception in your function code.

Return Value Persistence​

The final return value of your function is always saved to the database when the task completes, regardless of browser state.

Pipes​

When a pipe's pipe() method returns (or its generator finishes yielding), the streaming handler saves the final result at completion:

  • If ENABLE_REALTIME_CHAT_SAVE is on: intermediate chunks are saved during streaming
  • If ENABLE_REALTIME_CHAT_SAVE is off: the full final content is saved in one write at completion

Either way, the final assistant message is always persisted. When you reopen the chat, it will be there.

Tools​

Return typeWhat happensPersisted?
HTMLResponse (with Content-Disposition: inline)HTML body extracted β†’ added to embeds β†’ emitted as "embeds" eventβœ… Yes
HTMLResponse (without inline)Body decoded as plain text tool resultβœ… Yes
str / dictUsed as tool result textβœ… Yes
list (MCP)Text items joined, images converted to filesβœ… Yes

Actions​

Actions go through the same return handling as tools. The same persistence rules apply:

Return typeWhat happensPersisted?
HTMLResponse (with Content-Disposition: inline)HTML body extracted β†’ added to embeds β†’ emitted as "embeds" eventβœ… Yes
HTMLResponse (without inline)Body decoded as plain text resultβœ… Yes
str / dictUsed as action result textβœ… Yes

Filters​

Filters transform form_data in the pipeline β€” they don't return results to the user directly. However, filters do receive __event_emitter__ and can emit persisted event types like "status", "embeds", "message", etc.

Function Type Capabilities Matrix​

CapabilityToolsActionsPipesFilters
__event_emitter__βœ…βœ…βœ…βœ…
__event_call__βœ…βœ…βœ…βœ…
Return value β†’ user responseβœ…βœ…βœ…βŒ (modifies form_data)
HTMLResponse β†’ Rich UI embedβœ…βœ…βŒβŒ

Practical Summary​

If you want your function's output to survive a closed browser tab, follow these rules:

  1. Always return your final answer from your pipe(), tool, or action function β€” the return value is always saved
  2. Use short event type names ("status", "message", "embeds", "files", "source") for DB persistence
  3. Avoid relying on "notification", "confirmation", "input", or "execute" for critical workflows β€” these require a live browser connection
  4. Rich UI HTML embeds ("embeds" type or HTMLResponse return) are persisted and will render when the user reopens the chat

πŸ“ Conclusion​

Events give you real-time, interactive superpowers inside Open WebUI. They let your code update content, trigger notifications, request user input, stream results, handle code, and much moreβ€”seamlessly plugging your backend intelligence into the chat UI.

  • Use __event_emitter__ for one-way status/content updates.
  • Use __event_call__ for interactions that require user follow-up (input, confirmation, execution).

Refer to this document for common event types and structures, and explore Open WebUI source code or docs for breaking updates or custom events!


Happy event-driven coding in Open WebUI! πŸš€