๐ช Filter Function: Modify Inputs and Outputs
Welcome to the comprehensive guide on Filter Functions in Open WebUI! Filters are a flexible and powerful plugin system for modifying data before it's sent to the Large Language Model (LLM) (input) or after itโs returned from the LLM (output). Whether youโre transforming inputs for better context or cleaning up outputs for improved readability, Filter Functions let you do it all.
This guide will break down what Filters are, how they work, their structure, and everything you need to know to build powerful and user-friendly filters of your own. Letโs dig in, and donโt worryโIโll use metaphors, examples, and tips to make everything crystal clear! ๐
๐ What Are Filters in Open WebUI?โ
Imagine Open WebUI as a stream of water flowing through pipes:
- User inputs and LLM outputs are the water.
- Filters are the water treatment stages that clean, modify, and adapt the water before it reaches the final destination.
Filters sit in the middle of the flowโlike checkpointsโwhere you decide what needs to be adjusted.
Hereโs a quick summary of what Filters do:
- Modify User Inputs (Inlet Function): Tweak the input data before it reaches the AI model. This is where you enhance clarity, add context, sanitize text, or reformat messages to match specific requirements.
- Intercept Model Outputs (Stream Function): Capture and adjust the AIโs responses as theyโre generated by the model. This is useful for real-time modifications, like filtering out sensitive information or formatting the output for better readability.
- Modify Model Outputs (Outlet Function): Adjust the AI's response after itโs processed, before showing it to the user. This can help refine, log, or adapt the data for a cleaner user experience.
Key Concept: Filters are not standalone models but tools that enhance or transform the data traveling to and from models.
Filters are like translators or editors in the AI workflow: you can intercept and change the conversation without interrupting the flow.
๐บ๏ธ Structure of a Filter Function: The Skeletonโ
Let's start with the simplest representation of a Filter Function. Don't worry if some parts feel technical at firstโweโll break it all down step by step!
๐ฆด Basic Skeleton of a Filterโ
from pydantic import BaseModel
from typing import Optional
class Filter:
# Valves: Configuration options for the filter
class Valves(BaseModel):
pass
def __init__(self):
# Initialize valves (optional configuration for the Filter)
self.valves = self.Valves()
def inlet(self, body: dict) -> dict:
# This is where you manipulate user inputs.
print(f"inlet called: {body}")
return body
def stream(self, event: dict) -> dict:
# This is where you modify streamed chunks of model output.
print(f"stream event: {event}")
return event
def outlet(self, body: dict) -> None:
# This is where you manipulate model outputs.
print(f"outlet called: {body}")
๐ ๐งฒ Toggle Filter Example: Adding Interactivity and Icons (New in Open WebUI 0.6.10)โ
Filters can do more than simply modify textโthey can expose UI toggles and display custom icons. For instance, you might want a filter that can be turned on/off with a user interface button, and displays a special icon in Open WebUIโs message input UI.
Hereโs how you could create such a toggle filter:
from pydantic import BaseModel, Field
from typing import Optional
class Filter:
class Valves(BaseModel):
pass
def __init__(self):
self.valves = self.Valves()
self.toggle = True # IMPORTANT: This creates a switch UI in Open WebUI
# TIP: Use SVG Data URI!
self.icon = """data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCAyNCAyNCIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBjbGFzcz0ic2l6ZS02Ij4KICA8cGF0aCBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGQ9Ik0xMiAxOHYtNS4yNW0wIDBhNi4wMSA2LjAxIDAgMCAwIDEuNS0uMTg5bS0xLjUuMTg5YTYuMDEgNi4wMSAwIDAgMS0xLjUtLjE4OW0zLjc1IDcuNDc4YTEyLjA2IDEyLjA2IDAgMCAxLTQuNSAwbTMuNzUgMi4zODNhMTQuNDA2IDE0LjQwNiAwIDAgMS0zIDBNMTQuMjUgMTh2LS4xOTJjMC0uOTgzLjY1OC0xLjgyMyAxLjUwOC0yLjMxNmE3LjUgNy41IDAgMSAwLTcuNTE3IDBjLjg1LjQ5MyAxLjUwOSAxLjMzMyAxLjUwOSAyLjMxNlYxOCIgLz4KPC9zdmc+Cg=="""
pass
async def inlet(
self, body: dict, __event_emitter__, __user__: Optional[dict] = None
) -> dict:
await __event_emitter__(
{
"type": "status",
"data": {
"description": "Toggled!",
"done": True,
"hidden": False,
},
}
)
return body
๐ผ๏ธ Whatโs happening?โ
- toggle = True creates a switch UI in Open WebUIโusers can manually enable or disable the filter in real time.
- icon (with a Data URI) will show up as a little image next to the filterโs name. You can use any SVG as long as itโs Data URI encoded!
- The
inletfunction uses the__event_emitter__special argument to broadcast feedback/status to the UI, such as a little toast/notification that reads "Toggled!"

You can use these mechanisms to make your filters dynamic, interactive, and visually unique within Open WebUIโs plugin ecosystem.
๐ฏ Key Components Explainedโ
1๏ธโฃ Valves Class (Optional Settings)โ
Think of Valves as the knobs and sliders for your filter. If you want to give users configurable options to adjust your Filterโs behavior, you define those here.
class Valves(BaseModel):
OPTION_NAME: str = "Default Value"
For example:
If you're creating a filter that converts responses into uppercase, you might allow users to configure whether every output gets totally capitalized via a valve like TRANSFORM_UPPERCASE: bool = True/False.
Configuring Valves with Dropdown Menus (Enums)โ
You can enhance the user experience for your filter's settings by providing dropdown menus instead of free-form text inputs for certain Valves. This is achieved using json_schema_extra with the enum keyword in your Pydantic Field definitions.
The enum keyword allows you to specify a list of predefined values that the UI should present as options in a dropdown.
Example: Creating a dropdown for color themes in a filter.
from pydantic import BaseModel, Field
from typing import Optional
# Define your available options (e.g., color themes)
COLOR_THEMES = {
"Plain (No Color)": [],
"Monochromatic Blue": ["blue", "RoyalBlue", "SteelBlue", "LightSteelBlue"],
"Warm & Energetic": ["orange", "red", "magenta", "DarkOrange"],
"Cool & Calm": ["cyan", "blue", "green", "Teal", "CadetBlue"],
"Forest & Earth": ["green", "DarkGreen", "LimeGreen", "OliveGreen"],
"Mystical Purple": ["purple", "DarkOrchid", "MediumPurple", "Lavender"],
"Grayscale": ["gray", "DarkGray", "LightGray"],
"Rainbow Fun": [
"red",
"orange",
"yellow",
"green",
"blue",
"indigo",
"violet",
],
"Ocean Breeze": ["blue", "cyan", "LightCyan", "DarkTurquoise"],
"Sunset Glow": ["DarkRed", "DarkOrange", "Orange", "gold"],
"Custom Sequence (See Code)": [],
}
class Filter:
class Valves(BaseModel):
selected_theme: str = Field(
"Monochromatic Blue",
description="Choose a predefined color theme for LLM responses. 'Plain (No Color)' disables coloring.",
json_schema_extra={"enum": list(COLOR_THEMES.keys())}, # KEY: This creates the dropdown
)
custom_colors_csv: str = Field(
"",
description="CSV of colors for 'Custom Sequence' theme (e.g., 'red,blue,green'). Uses xcolor names.",
)
strip_existing_latex: bool = Field(
True,
description="If true, attempts to remove existing LaTeX color commands. Recommended to avoid nested rendering issues.",
)
colorize_type: str = Field(
"sequential_word",
description="How to apply colors: 'sequential_word' (word by word), 'sequential_line' (line by line), 'per_letter' (letter by letter), 'full_message' (entire message).",
json_schema_extra={
"enum": [
"sequential_word",
"sequential_line",
"per_letter",
"full_message",
]
}, # Another example of an enum dropdown
)
color_cycle_reset_per_message: bool = Field(
True,
description="If true, the color sequence restarts for each new LLM response message. If false, it continues across messages.",
)
debug_logging: bool = Field(
False,
description="Enable verbose logging to the console for debugging filter operations.",
)
def __init__(self):
self.valves = self.Valves()
# ... rest of your __init__ logic ...
What's happening?
json_schema_extra: This argument inFieldallows you to inject arbitrary JSON Schema properties that Pydantic doesn't explicitly support but can be used by downstream tools (like Open WebUI's UI renderer)."enum": list(COLOR_THEMES.keys()): This tells Open WebUI that theselected_themefield should present a selection of values, specifically the keys from ourCOLOR_THEMESdictionary. The UI will then render a dropdown menu with "Plain (No Color)", "Monochromatic Blue", "Warm & Energetic", etc., as selectable options.- The
colorize_typefield also demonstrates anotherenumdropdown for different coloring methods.
Using enum for your Valves options makes your filters more user-friendly and prevents invalid inputs, leading to a smoother configuration experience.
2๏ธโฃ inlet Function (Input Pre-Processing)โ
The inlet function is like prepping food before cooking. Imagine youโre a chef: before the ingredients go into the recipe (the LLM in this case), you might wash vegetables, chop onions, or season the meat. Without this step, your final dish could lack flavor, have unwashed produce, or simply be inconsistent.
In the world of Open WebUI, the inlet function does this important prep work on the user input before itโs sent to the model. It ensures the input is as clean, contextual, and helpful as possible for the AI to handle.
๐ฅ Input:
body: The raw input from Open WebUI to the model. It is in the format of a chat-completion request (usually a dictionary that includes fields like the conversation's messages, model settings, and other metadata). Think of this as your recipe ingredients.
๐ Your Task:
Modify and return the body. The modified version of the body is what the LLM works with, so this is your chance to bring clarity, structure, and context to the input.
๐ณ Why Would You Use the inlet?โ
-
Adding Context: Automatically append crucial information to the userโs input, especially if their text is vague or incomplete. For example, you might add "You are a friendly assistant" or "Help this user troubleshoot a software bug."
-
Formatting Data: If the input requires a specific format, like JSON or Markdown, you can transform it before sending it to the model.
-
Sanitizing Input: Remove unwanted characters, strip potentially harmful or confusing symbols (like excessive whitespace or emojis), or replace sensitive information.
-
Streamlining User Input: If your modelโs output improves with additional guidance, you can use the
inletto inject clarifying instructions automatically!
๐ก Example Use Cases: Build on Food Prepโ
๐ฅ Example 1: Adding System Contextโ
Letโs say the LLM is a chef preparing a dish for Italian cuisine, but the user hasnโt mentioned "This is for Italian cooking." You can ensure the message is clear by appending this context before sending the data to the model.
def inlet(self, body: dict, __user__: Optional[dict] = None) -> dict:
# Add system message for Italian context in the conversation
context_message = {
"role": "system",
"content": "You are helping the user prepare an Italian meal."
}
# Insert the context at the beginning of the chat history
body.setdefault("messages", []).insert(0, context_message)
return body
๐ What Happens?
- Any user input like "What are some good dinner ideas?" now carries the Italian theme because weโve set the system context! Cheesecake might not show up as an answer, but pasta sure will.
๐ช Example 2: Cleaning Input (Remove Odd Characters)โ
Suppose the input from the user looks messy or includes unwanted symbols like !!!, making the conversation inefficient or harder for the model to parse. You can clean it up while preserving the core content.
def inlet(self, body: dict, __user__: Optional[dict] = None) -> dict:
# Clean the last user input (from the end of the 'messages' list)
last_message = body["messages"][-1]["content"]
body["messages"][-1]["content"] = last_message.replace("!!!", "").strip()
return body
๐ What Happens?
- Before:
"How can I debug this issue!!!"โก๏ธ Sent to the model as"How can I debug this issue"
Note: The user feels the same, but the model processes a cleaner and easier-to-understand query.
๐ How inlet Helps Optimize Input for the LLM:โ
- Improves accuracy by clarifying ambiguous queries.
- Makes the AI more efficient by removing unnecessary noise like emojis, HTML tags, or extra punctuation.
- Ensures consistency by formatting user input to match the modelโs expected patterns or schemas (like, say, JSON for a specific use case).
๐ญ Think of inlet as the sous-chef in your kitchenโensuring everything that goes into the model (your AI "recipe") has been prepped, cleaned, and seasoned to perfection. The better the input, the better the output!
๐ 3๏ธโฃ stream Hook (New in Open WebUI 0.5.17)โ
๐ What is the stream Hook?โ
The stream function is a new feature introduced in Open WebUI 0.5.17 that allows you to intercept and modify streamed model responses in real time.
Unlike outlet, which processes an entire completed response, stream operates on individual chunks as they are received from the model.
๐ ๏ธ When to Use the Stream Hook?โ
- Modify streaming responses before they are displayed to users.
- Implement real-time censorship or cleanup.
- Monitor streamed data for logging/debugging.