mirror of
https://github.com/open-webui/docs.git
synced 2026-07-22 10:35:30 -04:00
@@ -34,6 +34,7 @@ You cannot have Microsoft **and** Google as OIDC providers simultaneously.
|
||||
| `OAUTH_MERGE_ACCOUNTS_BY_EMAIL` | `false` | Merge OAuth logins based on matching email (caution: can be insecure if provider doesn't verify emails). |
|
||||
| `OAUTH_UPDATE_PICTURE_ON_LOGIN` | `false` | Update user profile pictures from OAuth provider with each login. |
|
||||
| `OAUTH_PICTURE_CLAIM` | `picture` | Field in the claim containing the profile picture. Set to empty string to disable picture updates (users receive default icon).|
|
||||
| `ENABLE_PROFILE_IMAGE_URL_FORWARDING` | `true` | When `true`, the backend issues a `302` redirect from `/api/v1/users/{id}/profile/image` to whatever external URL the IdP wrote into `profile_image_url`. Each browser then loads the avatar directly from that origin — leaking client IP, User-Agent, and Referer headers. Set to `false` to suppress the redirect; the IdP-supplied URL is still stored, but Open WebUI serves the bundled default image instead. **Recommendation:** disable unless your IdP returns avatars as `data:` URIs (which Open WebUI persists locally and is unaffected) or you accept the leak in exchange for showing IdP-hosted avatars. See [`ENABLE_PROFILE_IMAGE_URL_FORWARDING`](/reference/env-configuration#enable_profile_image_url_forwarding) for full semantics. |
|
||||
| `WEBUI_AUTH_SIGNOUT_REDIRECT_URL` | *empty* | Redirect users to this URL after signout. E.g., `https://your-company.com/logout-success` |
|
||||
| `WEBUI_SECRET_KEY` | *empty* | MUST be set - especially in clustered environments. Otherwise session issues and weird OAuth issues will occur |
|
||||
| `OAUTH_SESSION_TOKEN_ENCRYPTION_KEY` | `WEBUI_SECRET_KEY` | A secret key for encrypting OAuth tokens stored on the server. Must be shared across all instances in a cluster. |
|
||||
|
||||
@@ -77,6 +77,7 @@ Controls what users can share with the community or make public.
|
||||
| **Public Skills** | *(Requires Share Skills)* Ability to make skills public. |
|
||||
| **Share Notes** | **(Parent)** Ability to share Notes. |
|
||||
| **Public Notes** | *(Requires Share Notes)* Ability to make Notes public. |
|
||||
| **Chats Public Sharing** | *(Requires Share Chat)* Ability to make a chat share link reachable by anyone (including unauthenticated visitors). When disabled, users can still share chats with specific users or groups via the access-control selector, but the "Public" option is hidden for non-admins. Admins are always exempt. |
|
||||
|
||||
### 3. Chat Permissions
|
||||
Controls the features available to the user inside the chat interface.
|
||||
|
||||
@@ -87,6 +87,10 @@ Hold **Shift** in the User Menu to reveal pin/unpin buttons. Pinning Calendar ad
|
||||
- **Personal**: your default calendar for manual events (blue, auto-created on first visit)
|
||||
- **Scheduled Tasks**: virtual read-only overlay of automation schedules and runs (purple, only visible when the user has [Automations](/features/chat-conversations/chat-features/automations) access)
|
||||
|
||||
### Create a Calendar
|
||||
|
||||
Click the small **+** button next to the **Calendars** heading in the calendar sidebar to open the **New Calendar** modal. Enter a name and pick a color from the preset palette (blue, red, green, amber, violet, pink, cyan, orange) and click **Create**. The new calendar appears in the sidebar immediately and shows up in the calendar picker when creating or editing events. Each calendar belongs to its creator and can be shared via [Sharing Calendars](#sharing-calendars).
|
||||
|
||||
### Create an Event
|
||||
|
||||
1. Click **New Event** (sidebar or top bar) or click any day/hour cell on the calendar grid
|
||||
|
||||
@@ -33,6 +33,12 @@ To configure a speech to text provider:
|
||||
|
||||

|
||||
|
||||
### Restricting Accepted Audio Extensions
|
||||
|
||||
Open WebUI ships with an **Allowed Extensions** list under **Admin Settings → Audio → STT** that controls which audio file extensions the upload endpoint will accept (default: `mp3,wav,m4a,webm,ogg,flac,mp4,mpga,mpeg`). Uploads with any other extension are rejected with `400 Invalid audio file extension` before transcription runs.
|
||||
|
||||
This is enforced server-side in addition to the MIME-type check (`AUDIO_STT_SUPPORTED_CONTENT_TYPES`), so tightening it is a low-cost way to harden the STT endpoint against unexpected file types. Set the matching `AUDIO_STT_ALLOWED_EXTENSIONS` environment variable to seed the list at startup, or clear it in the UI to skip the extension check entirely.
|
||||
|
||||
## User-Level Settings
|
||||
|
||||
In addition the instance settings provisioned in the admin panel, there are also a couple of user-level settings that can provide additional functionality.
|
||||
|
||||
@@ -28,6 +28,12 @@ To share a chat:
|
||||
3. Then click on the **Share** option.
|
||||
4. Select either **Share to Open WebUI Community** (if **Enable Community Sharing** is toggled on by an **Admin**) or **Copy Link**.
|
||||
|
||||
:::info Sharing scope is controlled by RBAC
|
||||
|
||||
After generating a share link, the modal shows an **Access Control** selector for who can open it. The **Public** option (anyone with the link, including unauthenticated visitors) is gated by the **Chats Public Sharing** permission — when disabled, non-admin users only see options to grant access to specific users or groups. Admins always retain access to all options. See [RBAC Permissions](/features/authentication-access/rbac/permissions) and [`USER_PERMISSIONS_CHAT_ALLOW_PUBLIC_SHARING`](/reference/env-configuration#user_permissions_chat_allow_public_sharing) for configuration.
|
||||
|
||||
:::
|
||||
|
||||
#### Sharing to Open WebUI Community
|
||||
|
||||
When you select `Share to Open WebUI Community`:
|
||||
|
||||
@@ -23,6 +23,8 @@ Open WebUI provides a comprehensive set of chat features designed to enhance you
|
||||
|
||||
- **[🕒 Temporal Awareness](./temporal-awareness.mdx)**: How models understand time and date, including native tools for precise time calculations.
|
||||
|
||||
- **🎙️ Voice Mode**: Start a hands-free voice conversation with your model using the Voice Mode button. The overlay includes a mute/unmute toggle to temporarily silence your microphone without ending the session — useful in noisy or public environments.
|
||||
|
||||
- **[🧠 Reasoning & Thinking Models](./reasoning-models.mdx)**: Specialized support for models that generate internal chains of thought using thinking tags.
|
||||
|
||||
- **[💬 Follow-Up Prompts](./follow-up-prompts.md)**: Automatic generation of suggested follow-up questions after model responses.
|
||||
@@ -34,3 +36,5 @@ Open WebUI provides a comprehensive set of chat features designed to enhance you
|
||||
- **Skill Mentions**: Use `$` in the chat input to mention and activate [Skills](/features/workspace/skills) on-the-fly, injecting their manifests into the conversation.
|
||||
|
||||
- **Writing & Content Blocks**: Responses from models that include colon-fence blocks (e.g., `:::writing`, `:::code_execution`, `:::search_results`) are automatically rendered as formatted content in a styled container with a copy button. This is commonly used by newer OpenAI models to distinguish different types of output (prose, code results, search results) from the main response text.
|
||||
|
||||
- **Structured Response Editing**: Clicking the edit icon on an assistant response that contains tool calls, reasoning blocks, or code-interpreter output opens a structured editor instead of a plain textarea — each output item (text, thought, tool call, code block, web/file/computer search) becomes its own collapsible field, and a **JSON** toggle exposes the underlying [OpenAI Responses API](https://platform.openai.com/docs/api-reference/responses) payload for direct editing. Reasoning/thinking blocks are editable here too — the previous textarea editor reduced them to a non-editable `<details>` placeholder. Plain text-only responses keep the original textarea behavior. Editing rewrites the message's `output` array; Open WebUI re-derives the displayed `content` (including `<details>` blocks for thoughts and tool calls) on save, so the rendered message stays consistent.
|
||||
|
||||
@@ -124,38 +124,63 @@ This ensures the model has access to its previous thought process when deciding
|
||||
|
||||
### How Reasoning Is Sent Back
|
||||
|
||||
When building the next API request during a tool call loop, Open WebUI serializes reasoning as **text wrapped in tags** inside the assistant message's `content` field:
|
||||
There are two parallel paths, and which one applies depends on **how the prior turn's reasoning was captured** — not on the provider you're sending to.
|
||||
|
||||
```text
|
||||
<think>Let me search for the current weather data...</think>
|
||||
```
|
||||
#### Path 1 — Reasoning embedded in `content` (default for most providers)
|
||||
|
||||
The message structure looks like:
|
||||
When a model streams reasoning as `<think>…</think>` (or any other configured tag pair) inline in its content, Open WebUI keeps those tags as part of `message.content`. On the next turn the message is forwarded to the LLM verbatim, tags and all. Nothing is dropped, no provider-specific handling kicks in. This is the path you're most likely already on — it is also what the [Interleaved Thinking with Tool Calls](#interleaved-thinking-with-tool-calls) section above describes.
|
||||
|
||||
#### Path 2 — Reasoning captured into a structured `output` array
|
||||
|
||||
If the upstream stream emits `reasoning_content` / `reasoning` / `thinking` deltas (rather than text tags) — typical for **OpenAI o-series**, **DeepSeek's official API**, **llama.cpp** with reasoning enabled, or **Ollama** with the `--reasoning-parser` flag — Open WebUI captures those deltas into a separate `reasoning` item inside the assistant message's `output` array. The displayed `content` only contains a `<details type="reasoning">` block for the UI; the structured form lives on `output`.
|
||||
|
||||
When that message is replayed to the LLM (within a tool-call loop or on the next user turn), Open WebUI rebuilds it from `output` rather than forwarding the rendered `content`. The format used for the rebuilt reasoning depends on the **connection's `Provider` setting** (**Admin Settings → Connections → OpenAI → Provider** dropdown — options are `Default`, `Azure OpenAI`, `llama.cpp`; Ollama connections are detected automatically):
|
||||
|
||||
| Connection `Provider` | Rebuilt reasoning format |
|
||||
| :--- | :--- |
|
||||
| Ollama (auto-detected) | Wrapped in tags inside `content`: `<think>…</think>` |
|
||||
| `llama.cpp` | Top-level `reasoning_content` field on the assistant message |
|
||||
| `Default` / `Azure OpenAI` | Reasoning is **omitted** from the rebuilt message |
|
||||
|
||||
The "omit on default/strict" choice is deliberate: OpenAI, Azure OpenAI, Vertex AI, and similar Chat Completions APIs reject assistant messages with unknown fields, and they don't expect their own reasoning echoed back on the next call (they manage continuity stateside via `previous_response_id` for the Responses API, or simply don't need it). Pipe functions and filters can re-inject reasoning in a provider-specific shape when needed.
|
||||
|
||||
For Ollama, a rebuilt message looks like:
|
||||
|
||||
```json
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "<think>reasoning content here</think>",
|
||||
"content": "<think>Let me search for the current weather data...</think>",
|
||||
"tool_calls": [...]
|
||||
}
|
||||
```
|
||||
|
||||
For a `llama.cpp` connection it looks like:
|
||||
|
||||
```json
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "",
|
||||
"reasoning_content": "Let me search for the current weather data...",
|
||||
"tool_calls": [...]
|
||||
}
|
||||
```
|
||||
|
||||
### Provider Compatibility
|
||||
|
||||
Open WebUI follows the **OpenAI Chat Completions API standard**. Reasoning content is serialized as text within the message content field, not as provider-specific structured blocks.
|
||||
|
||||
| Provider Type | Compatibility |
|
||||
|--------------|---------------|
|
||||
| OpenAI-compatible APIs | ✅ Works — reasoning is in the content text |
|
||||
| Ollama | ✅ Works — Ollama processes the message content |
|
||||
| Anthropic (extended thinking) | ❌ Not supported — Anthropic requires structured `{"type": "thinking"}` blocks, use a pipe function |
|
||||
| OpenAI o-series (stateful) | ⚠️ Limited — reasoning is hidden/internal, nothing to capture |
|
||||
| Provider type | Path 1 (tags in `content`) | Path 2 (structured `output`) |
|
||||
| :--- | :--- | :--- |
|
||||
| OpenAI-compatible APIs that emit `<think>` tags inline (DeepSeek-R1 via Ollama, Qwen3 with the reasoning parser, etc.) | ✅ Round-trips automatically. | n/a |
|
||||
| Ollama (connection auto-tagged as `ollama`) | ✅ Round-trips. | ✅ Rebuilt as `<think>…</think>` in `content`. |
|
||||
| llama.cpp (connection *Provider* set to `llama.cpp`) | ✅ Round-trips if the model uses tags. | ✅ Rebuilt as `reasoning_content` field. |
|
||||
| OpenAI o-series (Responses API) | n/a — o-series doesn't expose reasoning text. | ⚠️ Reasoning continuity is handled by `previous_response_id`, not by re-sending content. |
|
||||
| OpenAI / Azure / Vertex AI (Chat Completions, *Provider* `Default`) | ✅ Tags pass through as text — most strict providers tolerate them. | ⚠️ Structured reasoning is omitted on rebuild (use a pipe/filter to re-inject if you need it). |
|
||||
| Anthropic (extended thinking) | ⚠️ Tags work, but the native API expects `{"type": "thinking"}` blocks for true extended thinking — use a [pipe function](/features/extensibility/plugin/functions/pipe). | ❌ Not natively supported. |
|
||||
|
||||
### Important Notes
|
||||
|
||||
- **Within-turn preservation**: Reasoning is preserved and sent back to the API within the same turn (while tool calls are being processed).
|
||||
- **Cross-turn behavior**: Reasoning content **is** sent back to the API across turns. When building messages for subsequent requests, Open WebUI serializes the reasoning content with its original tags (e.g., `<think>...</think>`) and includes it in the assistant message's `content` field. This allows the model to maintain context of its previous reasoning throughout the conversation.
|
||||
- **Text-based serialization**: Reasoning is sent as text wrapped in tags (e.g., `<think>thinking content</think>`), not as structured content blocks. This works with most OpenAI-compatible APIs but may not align with provider-specific formats like Anthropic's extended thinking content blocks.
|
||||
- **Detection is unchanged.** Reasoning is detected from incoming streams the same way it always was — `<think>`-style tag matching for Path 1, `reasoning_content` / `reasoning` / `thinking` deltas for Path 2. The provider-aware behavior added here only governs how Path 2 reasoning is **rebuilt** when the message is replayed.
|
||||
- **Within-turn vs. cross-turn use the same logic.** The same rebuild rules apply whether Open WebUI is sending the message inside a multi-step tool-call loop or building the payload for a fresh user turn.
|
||||
- **Most users are on Path 1.** Models that emit `<think>` tags inline (the DeepSeek-R1 family on Ollama, Qwen3, MiniMax M2.5, etc.) keep their reasoning in `content` and continue to round-trip without any provider configuration.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -23,6 +23,15 @@ Having issues with web search? Check out the [Web Search Troubleshooting Guide](
|
||||
|
||||
## Brave API
|
||||
|
||||
Open WebUI ships with two Brave-backed search engines that share the same API key (`BRAVE_SEARCH_API_KEY`) and the same rate limits, but hit different endpoints:
|
||||
|
||||
| Engine | Endpoint | Behavior |
|
||||
| :--- | :--- | :--- |
|
||||
| `brave` | `/res/v1/web/search` | Classic web search. Returns short snippets; Open WebUI then scrapes each result URL to build the LLM context. |
|
||||
| `brave_llm_context` | `/res/v1/llm/context` | LLM-optimized search. Returns full pre-extracted, relevance-scored page passages directly. Skips the post-search scrape entirely. Pull size is bounded by [`BRAVE_SEARCH_CONTEXT_TOKENS`](/reference/env-configuration#brave_search_context_tokens) (default `8192`, range `1024`–`32768`). |
|
||||
|
||||
Pick `brave_llm_context` when you want fewer round-trips and higher-fidelity passages without the scraping step; pick `brave` when you need the classic snippet-then-scrape flow (e.g. when scraping is doing useful normalization in your pipeline). Both engines automatically retry once after HTTP 429 with a 1-second backoff.
|
||||
|
||||
### Docker Compose Setup
|
||||
|
||||
Add the following environment variables to your Open WebUI `docker-compose.yaml` file:
|
||||
@@ -32,8 +41,9 @@ services:
|
||||
open-webui:
|
||||
environment:
|
||||
ENABLE_WEB_SEARCH: True
|
||||
WEB_SEARCH_ENGINE: "brave"
|
||||
WEB_SEARCH_ENGINE: "brave" # or "brave_llm_context"
|
||||
BRAVE_SEARCH_API_KEY: "YOUR_API_KEY"
|
||||
# BRAVE_SEARCH_CONTEXT_TOKENS: 8192 # only for brave_llm_context
|
||||
WEB_SEARCH_RESULT_COUNT: 3
|
||||
WEB_SEARCH_CONCURRENT_REQUESTS: 1
|
||||
```
|
||||
|
||||
@@ -82,8 +82,9 @@ Use this when you already have a client ID/client secret from your identity prov
|
||||
4. Enter your MCP server **URL**.
|
||||
5. In **Auth**, select **OAuth 2.1 (Static)**.
|
||||
6. Enter **Client ID** and **Client Secret**.
|
||||
7. Click **Register Client**.
|
||||
8. Click **Save**.
|
||||
7. *(Optional)* Enter an **OAuth Server URL** if your identity provider lives on a different host than the MCP resource server. When empty, Open WebUI fetches the OAuth discovery document from the MCP **URL**; when set, discovery and the static-credentials registration both run against this URL instead. Useful for setups where the MCP server proxies a corporate IdP at a separate origin.
|
||||
8. Click **Register Client**.
|
||||
9. Click **Save**.
|
||||
|
||||
After saving:
|
||||
|
||||
@@ -98,6 +99,14 @@ After saving:
|
||||
|
||||
:::
|
||||
|
||||
:::info Resource Indicators (RFC 8707)
|
||||
|
||||
If the MCP server publishes a `resource` field in its [RFC 9728 Protected Resource Metadata document](https://datatracker.ietf.org/doc/html/rfc9728) (`/.well-known/oauth-protected-resource`), Open WebUI captures it during discovery and forwards it as the `resource` parameter on the **authorization redirect**, on the **token exchange**, and on every **refresh-token call**. This sets the `aud` claim correctly on issued tokens and is required by identity providers that validate audiences strictly per [RFC 8707](https://datatracker.ietf.org/doc/html/rfc8707).
|
||||
|
||||
No configuration is needed on the Open WebUI side — the resource indicator is discovered automatically. If your IdP rejects MCP tool calls with `invalid_audience` or similar errors, confirm that your MCP server's protected-resource metadata document advertises the correct `resource` URL.
|
||||
|
||||
:::
|
||||
|
||||
:::warning OAuth 2.1 Tools Cannot Be Set as Default Tools
|
||||
**Do not set OAuth 2.1 MCP tools as default/pre-enabled tools on a model.** The OAuth 2.1 authorization flow requires an interactive browser redirect (user consent, callback) that cannot happen transparently during a chat completion request.
|
||||
|
||||
@@ -111,6 +120,19 @@ If an OAuth 2.1 tool is set as a default and the user hasn't previously authenti
|
||||
If you are running Open WebUI in **Docker** and your MCP server is on the **host machine**:
|
||||
* Use `http://host.docker.internal:<port>` (e.g., `http://host.docker.internal:3000/sse`) instead of `localhost`.
|
||||
|
||||
### Custom Headers
|
||||
|
||||
Both MCP and OpenAPI tool-server connections accept a free-form **Headers** field (JSON object) that is merged into every outbound request. Header values support a small set of template tokens, expanded server-side at request time so you can route a single connection to per-user, per-chat, or per-message backend logic without provisioning separate connections:
|
||||
|
||||
| Token | Replaced with |
|
||||
| :--- | :--- |
|
||||
| `{{USER_ID}}` | The calling user's ID. |
|
||||
| `{{USER_NAME}}` | The calling user's display name. |
|
||||
| `{{CHAT_ID}}` | The current chat ID (empty in non-chat contexts like the **Verify Connection** button). |
|
||||
| `{{MESSAGE_ID}}` | The current message ID (empty in non-chat contexts). |
|
||||
|
||||
Unknown tokens are passed through as literal text. Non-string header values are coerced to strings before substitution. The same tokens are honored on custom headers attached to OpenAI-compatible model connections in **Admin Settings → Connections → OpenAI**, so you can use the feature for tenant routing or audit-trail propagation across both surfaces.
|
||||
|
||||
### Function Name Filter List
|
||||
|
||||
This field restricts which tools are exposed to the LLM.
|
||||
|
||||
@@ -248,6 +248,108 @@ await __event_emitter__(
|
||||
|
||||
---
|
||||
|
||||
### `embeds` or `chat:message:embeds`
|
||||
|
||||
Attach Rich UI iframes to the assistant message. Each entry in the `embeds` list is a **string** that is fed directly to the embed iframe's `src`/`srcdoc`:
|
||||
|
||||
- A `http://`, `https://`, or `//`-prefixed value is loaded as a URL.
|
||||
- Anything else is treated as **raw HTML** and rendered inline (Open WebUI auto-detects the two cases).
|
||||
|
||||
The full payload shape is:
|
||||
|
||||
```python
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "embeds", # short name; persists to the DB
|
||||
"data": {
|
||||
"embeds": ["<html>…</html>", "https://example.com/widget"],
|
||||
"replace": False, # optional, default False
|
||||
},
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
#### Append vs. replace
|
||||
|
||||
By default the new entries are **appended** to whatever is already on the message — repeated emits stack up. For a single live widget you want to update in place, set `data.replace: True`; the entire `embeds` array on the message is overwritten with the payload's array. (Pass an empty list with `replace: True` to clear all embeds for the message.)
|
||||
|
||||
| Mode | Behavior | Typical use |
|
||||
| :--- | :--- | :--- |
|
||||
| `replace` omitted / `False` | Each new payload's items are appended to `message.embeds`. | One-shot tools / actions that emit a single embed at the end. |
|
||||
| `replace: True` | The message's `embeds` array is replaced with the payload's array. | Long-running pipes that keep refreshing one widget (progress bar, live dashboard, polling status card). |
|
||||
|
||||
The `replace` flag was added so that re-emitting an embed doesn't leave stale copies stacked in the conversation when the user reloads — only the latest version is persisted.
|
||||
|
||||
#### Example: append a single embed at the end of a tool
|
||||
|
||||
```python
|
||||
async def get_weather_dashboard(self, city: str, __event_emitter__) -> str:
|
||||
html = build_dashboard_html(city) # your renderer
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "embeds",
|
||||
"data": {"embeds": [html]},
|
||||
}
|
||||
)
|
||||
return f"Rendered weather dashboard for {city}."
|
||||
```
|
||||
|
||||
This is equivalent to returning an `HTMLResponse` from a tool — the [Rich UI Embedding](/features/extensibility/plugin/development/rich-ui) doc covers that path and is the recommended option whenever a single embed is enough.
|
||||
|
||||
#### Example: replace a live progress widget from a pipe
|
||||
|
||||
```python
|
||||
async def pipe(self, body: dict, __event_emitter__):
|
||||
for step in range(1, 6):
|
||||
progress_html = f"""
|
||||
<div style="font-family:system-ui;padding:1rem;">
|
||||
<h3>Indexing your library</h3>
|
||||
<progress value="{step}" max="5" style="width:100%;"></progress>
|
||||
<p>Step {step} of 5…</p>
|
||||
</div>
|
||||
"""
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "embeds",
|
||||
"data": {
|
||||
"embeds": [progress_html],
|
||||
"replace": True, # overwrite, don't stack
|
||||
},
|
||||
}
|
||||
)
|
||||
await asyncio.sleep(2)
|
||||
|
||||
# Final state — also replaces, leaving exactly one embed on the message
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "embeds",
|
||||
"data": {"embeds": ["<div>✅ Library indexed.</div>"], "replace": True},
|
||||
}
|
||||
)
|
||||
yield "Indexing complete."
|
||||
```
|
||||
|
||||
When the user reloads the chat, `message.embeds` contains exactly one entry — the most recent one — instead of five stacked progress cards.
|
||||
|
||||
#### Example: clear all embeds
|
||||
|
||||
```python
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "embeds",
|
||||
"data": {"embeds": [], "replace": True},
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
#### Notes
|
||||
|
||||
- **Persistence requires the short name.** Emit `"embeds"` (or `"chat:message:embeds"`) but be aware only the short name `"embeds"` is persisted to the database — see the [persistence table](#which-event-types-are-persisted-to-the-database) below. The `replace` flag has no effect on the unpersisted alias.
|
||||
- **Sandbox and sizing.** Each embed is rendered inside a sandboxed iframe with `allow-scripts`, `allow-popups`, and `allow-downloads` enabled by default. Same-origin and form submissions are user-toggled. For auto-resize behavior, height reporting, and the Chart.js / Alpine auto-injection details, see [Rich UI Embedding → Sandbox and Security](/features/extensibility/plugin/development/rich-ui#sandbox-and-security).
|
||||
- **External tools.** External-tool callers can post the same `embeds` payload (including `replace: true`) to `/api/v1/chats/{chatId}/messages/{messageId}/event` — see [External Tools below](#external-tools-openapi--mcp).
|
||||
|
||||
---
|
||||
|
||||
### `chat:title`
|
||||
|
||||
**Update the chat's title:**
|
||||
@@ -713,6 +815,7 @@ External tools can emit the same event types as native tools:
|
||||
- `chat:message:delta` / `message` – Append content to the message
|
||||
- `chat:message` / `replace` – Replace message content
|
||||
- `files` / `chat:message:files` – Attach files
|
||||
- `embeds` / `chat:message:embeds` – Attach Rich UI iframes; pass `data.replace: true` to overwrite the existing embeds array (see [`embeds`](#embeds-or-chatmessageembeds))
|
||||
- `source` / `citation` – Add citations
|
||||
|
||||
:::note
|
||||
@@ -818,7 +921,7 @@ The event emitter writes certain event types directly to the database **regardle
|
||||
| `status` | Appended to the message's `statusHistory` array |
|
||||
| `message` | Appended to the message's `content` field |
|
||||
| `replace` | Overwrites the message's `content` field |
|
||||
| `embeds` | Appended to the message's `embeds` array (Rich UI HTML) |
|
||||
| `embeds` | Appended to the message's `embeds` array (Rich UI HTML), or replaced when `data.replace: true` is set. See [`embeds`](#embeds-or-chatmessageembeds) for the full payload shape and a live-progress example. |
|
||||
| `files` | Appended to the message's `files` array |
|
||||
| `source` / `citation` | Appended to the message's `sources` array |
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ It is a **visibility / gating flag**, read once at request-dispatch time — **n
|
||||
- **`self.toggle` is never mutated by the UI.** Inside `inlet()` it is always whatever you set in `__init__` — which will be `True` for every call that actually runs, because if the user had disabled the filter, `inlet()` wouldn't be running. Don't build logic that reads `self.toggle` at runtime; it's not a live on/off signal.
|
||||
|
||||
:::warning Upgrading from pre-0.9.0 filters
|
||||
Some older filters used a pattern like `if self.toggle: enable_feature() else: disable_feature()` inside `inlet()`, hoping to read the UI state back on every request. **That pattern was never reliable and is effectively dead on 0.9.0+.** `inlet()` simply isn't called when the filter is disabled in the UI, so there is no "else" branch to hit. The correct migration is to stop branching on `self.toggle` entirely and just do the work unconditionally — the user controls whether `inlet()` runs by selecting/deselecting the chip. If you need user-driven config (a numeric threshold, a target language, etc.), expose it through [`UserValves`](#⚙%EF%B8%8F-filter-administration--configuration) instead; clicking the chip opens the user's valves modal automatically.
|
||||
Some older filters used a pattern like `if self.toggle: enable_feature() else: disable_feature()` inside `inlet()`, hoping to read the UI state back on every request. **That pattern was never reliable and is effectively dead on 0.9.0+.** `inlet()` simply isn't called when the filter is disabled in the UI, so there is no "else" branch to hit. The correct migration is to stop branching on `self.toggle` entirely and just do the work unconditionally — the user controls whether `inlet()` runs by selecting/deselecting the chip. If you need user-driven config (a numeric threshold, a target language, etc.), expose it through [`UserValves`](#filter-administration--configuration) instead; clicking the chip opens the user's valves modal automatically.
|
||||
:::
|
||||
|
||||
#### How users interact with a toggleable filter
|
||||
@@ -664,6 +664,36 @@ The hashed user UUID is added as a top-level body parameter and forwarded direct
|
||||
Filters can only modify the **request body** (`form_data`). Outbound HTTP headers are constructed separately and cannot be influenced from a filter. To add custom headers to API requests, use the **Admin Panel → Settings → Connections → OpenAI API** headers configuration.
|
||||
:::
|
||||
|
||||
#### Injecting OpenAI-Style `tools` From a Filter
|
||||
|
||||
A filter inlet can also append OpenAI-style function-calling tools to `body["tools"]`. These are **merged with** the tools Open WebUI resolves server-side from `tool_ids`, MCP servers, and the model's built-in tools — they don't replace them.
|
||||
|
||||
```python
|
||||
class Filter:
|
||||
async def inlet(self, body: dict) -> dict:
|
||||
body.setdefault("tools", []).append({
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "lookup_user_tier",
|
||||
"description": "Return the caller's billing tier.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {"user_id": {"type": "string"}},
|
||||
"required": ["user_id"],
|
||||
},
|
||||
},
|
||||
})
|
||||
return body
|
||||
```
|
||||
|
||||
Behavior:
|
||||
|
||||
- **Native function calling** (the default). Filter-injected tools are appended to the server-resolved tool list before the chat completion is sent. Both sets of tools are visible to the model on the same request.
|
||||
- **Tools provided by the original API caller.** If the request that *entered* the filter pipeline already had `body["tools"]` (e.g. an external client called `/api/chat/completions` with its own `tools` array), those caller-provided tools take precedence and Open WebUI **skips server-side tool resolution entirely**. Filter inlets should additively append in that case too — the caller's tools and yours both go to the LLM.
|
||||
- **Non-native function calling.** Server-side tool resolution still runs through Open WebUI's prompt-driven tool handler; filter-injected tools are forwarded to the LLM but the runtime executor for them is up to whatever you wired into the upstream call.
|
||||
|
||||
Use this for tools that don't have a corresponding registered Tool in the workspace — for example, transient per-request capabilities you want the model to know about on a specific user's chats only.
|
||||
|
||||
---
|
||||
|
||||
### 🔍 Resolving the Base Model (`__model__`)
|
||||
|
||||
@@ -31,6 +31,17 @@ While OpenAPI tools are powerful for extending Open WebUI with external services
|
||||
|
||||
If you need interactive UI feedback (confirmations, user input prompts), consider implementing your tool as a [native Python Tool](/features/extensibility/plugin/tools/development) instead.
|
||||
|
||||
## ✅ Supported Spec Shapes
|
||||
|
||||
Open WebUI parses tool-server specs against the OpenAPI 3.x path-item model. The parser:
|
||||
|
||||
- Recognizes only the eight standard HTTP method keys (`get`, `put`, `post`, `delete`, `options`, `head`, `patch`, `trace`) as operations. Any other key under a path — including extension keys (`x-codeSamples`, `x-internal`, etc.), `summary`, `description`, `servers`, and `parameters` — is skipped during operation discovery so it never causes a parse error.
|
||||
- Honors **path-level `parameters`**: parameters declared once at the path level apply to every operation under that path. Operation-level parameters with the same `(name, in)` pair override path-level parameters, matching the OpenAPI specification.
|
||||
- URL-encodes path parameter values (`{id}` substitution) and query parameter values, so values containing spaces, `/`, `&`, `?`, or other reserved characters are transmitted correctly.
|
||||
- Falls back gracefully between JSON and YAML when fetching the spec — `.yaml`/`.yml` URLs are parsed as YAML; everything else is tried as JSON first and as YAML on failure.
|
||||
|
||||
Specs that put `parameters` at the path level, expose extension keys, or rely on path-item metadata fields work without changes.
|
||||
|
||||
## 🚀 Quickstart
|
||||
|
||||
Get started quickly with our reference FastAPI-based implementations provided in the `servers/` directory. (You can adapt these examples into your preferred stack as needed, such as using [FastAPI](https://fastapi.tiangolo.com/), [FastOpenAPI](https://github.com/mr-fatalyst/fastopenapi) or any other OpenAPI-compatible library):
|
||||
|
||||
@@ -123,6 +123,12 @@ Pin important notes to the sidebar for quick access. Pinned notes appear in a de
|
||||
|
||||
The sidebar Notes folder only appears when at least one note is pinned and the Notes feature is enabled.
|
||||
|
||||
:::info Pins are personal
|
||||
|
||||
Pinning a note only affects **your** sidebar — collaborators with access to the same note can pin or unpin it independently without changing what you see. Pinned notes are listed in pin-time order (newest first), and toggling a pin no longer counts as an edit, so it does not bump the note's "Updated" timestamp. Pre-existing pins from before this change are kept for the note's owner only; other users will need to re-pin shared notes they want in their own sidebar.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Management and Sharing
|
||||
|
||||
@@ -17,6 +17,10 @@ Click folders to navigate, and click files to preview them. A breadcrumb bar at
|
||||
|
||||

|
||||
|
||||
### Sorting
|
||||
|
||||
Use the toolbar above the file list to sort by **Name** or **Date Modified**, ascending or descending. Folders always group ahead of files, so the sort order only reorders entries within each group. Each row shows its modified time next to the size, making it easier to find the latest output the AI just dropped into a working directory.
|
||||
|
||||
---
|
||||
|
||||
## Previewing files
|
||||
|
||||
@@ -22,7 +22,7 @@ flowchart LR
|
||||
E --> F["Orchestrator provisions<br/>container with policy spec"]
|
||||
```
|
||||
|
||||
1. **Admin creates policies** on the orchestrator via its REST API (see [API reference](#api-reference) below).
|
||||
1. **Admin creates policies** on the orchestrator via its REST API (see the API reference at the end of this page).
|
||||
2. **Admin creates terminal connections** in Open WebUI under **Settings → Connections → Open Terminal**. Each connection includes a `policy_id` field that maps it to a policy on the orchestrator.
|
||||
3. **Users open a terminal.** Open WebUI routes the request through `/p/{policy_id}/...`, and the orchestrator provisions (or reuses) a container matching that policy's spec.
|
||||
|
||||
|
||||
@@ -73,12 +73,17 @@ The system prompt defines the behavior and persona. Use dynamic variables for co
|
||||
| `{{ CURRENT_DATE }}` | `2024-10-27` |
|
||||
| `{{ CURRENT_TIME }}` | `14:30:05` |
|
||||
| `{{ USER_NAME }}` | `Admin` |
|
||||
| `{{ USER_GROUPS }}` | `Engineering, Beta Testers` (comma-separated; empty if the user is in no groups) |
|
||||
|
||||
```
|
||||
You are a helpful assistant for {{ USER_NAME }}.
|
||||
The current date is {{ CURRENT_DATE }}.
|
||||
```
|
||||
|
||||
:::tip Group-aware system prompts
|
||||
`{{ USER_GROUPS }}` lets a single shared model adapt its behavior to the caller's RBAC groups — e.g. *"You may discuss internal roadmap items only when `{{ USER_GROUPS }}` contains 'Engineering'."* The placeholder is resolved server-side at chat time, and the database lookup runs only when the variable is actually referenced in the template.
|
||||
:::
|
||||
|
||||
### Capabilities and bindings
|
||||
|
||||
Toggle what the model can do and bind resources:
|
||||
|
||||
@@ -86,6 +86,7 @@ Automatically replaced with their value at runtime:
|
||||
| `{{USER_AGE}}` | Age calculated from birth date (unreplaced if not set) |
|
||||
| `{{USER_LANGUAGE}}` | Your selected language |
|
||||
| `{{USER_LOCATION}}` | Your location (requires HTTPS + Settings > Interface toggle) |
|
||||
| `{{USER_GROUPS}}` | Comma-separated list of the groups the user belongs to (empty if none). Resolved from the database only when the placeholder is present. |
|
||||
|
||||
### Custom input variables
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ Open WebUI is a self-hosted application that gives authenticated users access to
|
||||
|
||||
This guide covers the configuration options available for hardening your deployment. Each section explains what a setting does, what the default is, and how to change it. It is not an exhaustive security guide, and securing your deployment is ultimately your responsibility. Your environment, compliance requirements, and threat model will determine which of these are relevant to you.
|
||||
|
||||
:::tip Network Placement
|
||||
## Network Placement
|
||||
|
||||
Open WebUI is built for private, trusted networks, similar to other self-hosted infrastructure like databases, container registries, and CI servers. Most deployments run behind a corporate firewall, VPN, or other network boundary where access is limited to known users.
|
||||
|
||||
@@ -21,8 +21,6 @@ For organizations where security is a priority, the recommended deployment place
|
||||
|
||||
DDoS protection and brute-force prevention (rate limiting, connection throttling, fail2ban) should be handled at the proxy or network layer.
|
||||
|
||||
:::
|
||||
|
||||
If you are deploying Open WebUI for the first time, start with the [Quick Reference](#quick-reference) at the bottom of this page for a prioritized summary, then read the sections relevant to your setup.
|
||||
|
||||
---
|
||||
@@ -547,6 +545,18 @@ WEB_FETCH_FILTER_LIST=!internal.yourcompany.com,!10.0.0.0/8
|
||||
|
||||
Prefix entries with `!` to block them.
|
||||
|
||||
### Profile image URL forwarding
|
||||
|
||||
The user and model profile-image endpoints can issue a `302 Found` redirect to whatever origin is stored in `profile_image_url` so that externally-hosted avatars (e.g. Gravatar via an upstream identity provider) display in the UI. That redirect causes the user's browser to make a request directly to the external origin, leaking client IP, User-Agent, and Referer headers — and an account whose `profile_image_url` was set to an attacker-controlled host can use that to deanonymize anyone who renders their avatar.
|
||||
|
||||
To suppress the redirect entirely and serve the bundled default image instead:
|
||||
|
||||
```bash
|
||||
ENABLE_PROFILE_IMAGE_URL_FORWARDING=false
|
||||
```
|
||||
|
||||
Default is `true` so existing deployments relying on external avatars keep working. Data URIs and same-origin/static images are unaffected by this flag — they continue to render normally.
|
||||
|
||||
### File upload limits
|
||||
|
||||
By default, there are no size or count limits on uploaded files. To prevent storage exhaustion:
|
||||
@@ -568,7 +578,7 @@ RAG_ALLOWED_FILE_EXTENSIONS=.pdf,.txt,.md,.docx,.csv
|
||||
|
||||
Tools and Functions run arbitrary Python code on your server with the same access as the Open WebUI process. This is fundamental to how they work, and it means they can read files, make network requests, access environment variables, and interact with the database.
|
||||
|
||||
For details on the security model, see the [Security Policy](/security#tools-functions-and-pipelines-security).
|
||||
For details on the security model, see the [Security Policy](/security/security-policy#tools-functions-and-pipelines-security).
|
||||
|
||||
Because Tools and Functions execute server-side code, any user with permission to create or import them effectively has the same level of access as the Open WebUI process itself. This is inherent to how extensibility works. By default, only administrators can create and import Tools and Functions. The settings below control these permissions.
|
||||
|
||||
@@ -712,7 +722,7 @@ The table below summarizes the key hardening actions covered in this guide. Each
|
||||
|
||||
| Action | Default | Recommended for production |
|
||||
|---|---|---|
|
||||
| [Keep on private network](#hardening-open-webui) | No restriction | VPN, firewall, or zero-trust proxy |
|
||||
| [Keep on private network](#network-placement) | No restriction | VPN, firewall, or zero-trust proxy |
|
||||
| [Serve over HTTPS](#https-and-tls) | HTTP | HTTPS via reverse proxy |
|
||||
| [Set `WEBUI_SECRET_KEY`](#secret-key) (multi-replica) | Auto-generated | Explicit shared key |
|
||||
| [Review signup policy](#registration) | Disabled after first user | Keep disabled or use `pending` role |
|
||||
|
||||
@@ -382,6 +382,17 @@ UVICORN_WORKERS=1
|
||||
ENABLE_DB_MIGRATIONS=false
|
||||
```
|
||||
|
||||
### Security defaults to revisit at scale
|
||||
|
||||
A few defaults that are reasonable for single-user evaluation become less so once you put the deployment behind SSO and serve real users. The full discussion lives in the [Hardening guide](/getting-started/advanced-topics/hardening); the items most often missed in enterprise rollouts:
|
||||
|
||||
- **Disable external profile image redirects** — `ENABLE_PROFILE_IMAGE_URL_FORWARDING=false`. By default the user/model profile-image endpoints `302` to whatever external URL `profile_image_url` holds, which makes every browser viewing an avatar leak its IP, User-Agent, and Referer to that origin. Set this to `false` for shared deployments **unless** your IdP supplies avatars only as `data:` URIs (which Open WebUI persists locally and is unaffected) or you have a deliberate reason to keep IdP-hosted avatars rendering — e.g. your `OAUTH_PICTURE_CLAIM` returns Google/Gravatar URLs and you want them to display. See [SSO configuration](/features/authentication-access/auth/sso) for the matching OAuth picture-claim settings.
|
||||
- **Set `WEBUI_SECRET_KEY` and `OAUTH_SESSION_TOKEN_ENCRYPTION_KEY`** to the same value across every replica. Without this, sessions break on rolling restart and OAuth tokens written by one pod cannot be decrypted by another.
|
||||
- **Lower `JWT_EXPIRES_IN`** from the four-week default if your deployment carries sensitive data, especially if Redis is not yet in place to revoke tokens on signout. See [Token Revocation](/getting-started/advanced-topics/hardening#token-revocation).
|
||||
- **Disable `ENABLE_OAUTH_ID_TOKEN_COOKIE`** (`false`) once all clients are on the new server-side session model. The legacy cookie carried the raw IdP id_token to the browser; the new model keeps it server-side.
|
||||
|
||||
These are configuration defaults, not new features — the existing knobs simply matter more once a deployment has multiple users and a real identity provider in front of it.
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference: When Do I Need What?
|
||||
|
||||
@@ -105,6 +105,7 @@ To control and query your locally running model directly from Open WebUI:
|
||||
- **URL**: `http://127.0.0.1:10000/v1`
|
||||
(Or use `http://host.docker.internal:10000/v1` if running WebUI inside Docker. **Note the `/v1` at the end!**)
|
||||
- **API Key**: `none` (leave blank or type specific key if configured)
|
||||
- **Provider**: pick `llama.cpp` from the **Provider** dropdown. This unlocks the loaded-model indicator and the admin **Eject** button in the model selector — see [Unloading Loaded Models](#unloading-loaded-models) below. Leave at `Default` if you do not need that feature.
|
||||
|
||||
💡 Once saved, Open WebUI will begin using your local Llama.cpp server as a backend!
|
||||
|
||||
@@ -125,6 +126,14 @@ If you've saved an unreachable URL and the UI becomes unresponsive, see the [Mod
|
||||
|
||||
---
|
||||
|
||||
## Unloading Loaded Models
|
||||
|
||||
Once a model is held in memory by your `llama-server` instance, Open WebUI marks it with a green "Loaded" indicator in the model selector. Admins also see an **Eject** button on the row that unloads the model without restarting the server — it calls `POST /api/models/unload`, which in turn hits llama.cpp's `POST /models/unload` endpoint on the OpenAI-compatible connection's root URL.
|
||||
|
||||
For this to work, the matching connection in **Admin Settings → Connections → OpenAI** must have its **Provider** set to `llama.cpp` (Open WebUI uses that hint to pick the correct unload mechanism). Connections left at the default OpenAI-compatible provider type return an error when an admin attempts to unload a model.
|
||||
|
||||
---
|
||||
|
||||
## Quick Tip: Try Out the Model via Chat Interface
|
||||
|
||||
Once connected, select the model from the Open WebUI chat menu and start interacting!
|
||||
|
||||
@@ -77,6 +77,14 @@ This method is perfect if you want to skip navigating through the Admin Settings
|
||||
|
||||
---
|
||||
|
||||
## Unloading Loaded Models
|
||||
|
||||
Open WebUI shows a green "Loaded" indicator next to any Ollama model that is currently kept warm by the runtime, and admins see an **Eject** button on the model row to unload it without restarting the server. Behind the scenes, Open WebUI calls `POST /api/models/unload` (admin-only), which forwards a `keep_alive=0` generate call to every Ollama node serving that model.
|
||||
|
||||
This is useful for freeing GPU/VRAM when juggling multiple large models, or for forcing a fresh load after pulling a new revision. Trying to unload a model that the connected provider does not support unloading returns an error rather than silently failing.
|
||||
|
||||
---
|
||||
|
||||
## Using Reasoning / Thinking Models
|
||||
|
||||
If you're using reasoning models like **DeepSeek-R1** or **Qwen3** that output thinking/reasoning content in `<think>...</think>` tags, you'll need to configure Ollama with a **reasoning parser** for proper display.
|
||||
|
||||
@@ -837,7 +837,7 @@ This cleaning process handles:
|
||||
- **Critical: Include `childrenIds`** on every message — the frontend uses this to build the message tree
|
||||
- No frontend code changes are required for this approach
|
||||
- The `stream: true` parameter allows for real-time response streaming if needed
|
||||
- `outlet()` filters run inline during `/api/chat/completions` when `chat_id` and `id` (message ID) are present in the request body. Pure API callers that omit these fields will have outlet silently skipped — see [Filter Functions: Enabling Outlet for Pure API Callers](/features/extensibility/plugin/functions/filter#enabling-outlet-for-pure-api-callers) for a workaround. The separate `/api/chat/completed` endpoint is deprecated and no longer needed
|
||||
- `outlet()` filters run inline during `/api/chat/completions` when `chat_id` and `id` (message ID) are present in the request body. Pure API callers that omit these fields will have outlet silently skipped — see [Filter Functions: Running outlet() for API Callers](/features/extensibility/plugin/functions/filter#running-outlet-for-api-callers-apichatcompleted) for a workaround. The separate `/api/chat/completed` endpoint is deprecated and no longer needed
|
||||
- Background tasks like title generation can be controlled via the `background_tasks` object
|
||||
- Session IDs help maintain conversation context across requests
|
||||
- **Knowledge Integration:** Use the `files` array to include knowledge collections for RAG capabilities
|
||||
|
||||
@@ -91,6 +91,7 @@ Here is a complete list of tables in Open-WebUI's SQLite database. The tables ar
|
||||
| 35 | user | Maintains user profiles and account information |
|
||||
| 36 | automation | Stores user-defined scheduled automations |
|
||||
| 37 | automation_run | Stores execution history for automation runs |
|
||||
| 38 | pinned_note | Tracks per-user note pins (each row = one user pinning one note) |
|
||||
|
||||
Note: there are two additional tables in Open-WebUI's SQLite database that are not related to Open-WebUI's core functionality, that have been excluded:
|
||||
|
||||
@@ -577,11 +578,24 @@ Access control for resources (models, knowledge bases, tools, prompts, notes, fi
|
||||
| title | Text | nullable | Note title |
|
||||
| data | JSON | nullable | Note content and data |
|
||||
| meta | JSON | nullable | Note metadata |
|
||||
| is_pinned | Boolean | default=False, nullable | Whether the note is pinned to the sidebar |
|
||||
|
||||
| created_at | BigInteger | nullable | Creation timestamp |
|
||||
| updated_at | BigInteger | nullable | Last update timestamp |
|
||||
|
||||
Pin state is no longer stored on this table — the legacy `is_pinned` column was removed in migration `4de81c2a3af1` and replaced by a per-user [Pinned Note Table](#pinned-note-table). Pre-existing pins were backfilled to the note owner; the API surfaces `is_pinned` as a per-request join against the calling user's rows.
|
||||
|
||||
## Pinned Note Table
|
||||
|
||||
Per-user note pins. Each row records that one user has pinned one note for their own sidebar — pinning is private and does not affect any other user with access to the same note.
|
||||
|
||||
| **Column Name** | **Data Type** | **Constraints** | **Description** |
|
||||
| --------------- | ------------- | ---------------------------------------------------- | ------------------------------------------------ |
|
||||
| id | Text | PRIMARY KEY | Unique identifier (UUID) |
|
||||
| user_id | Text | NOT NULL | The user who pinned the note |
|
||||
| note_id | Text | NOT NULL, FOREIGN KEY(note.id) ON DELETE CASCADE | The pinned note |
|
||||
| created_at | BigInteger | NOT NULL | Pin creation timestamp (used for ordering) |
|
||||
|
||||
A `UNIQUE(user_id, note_id)` constraint prevents duplicate pins for the same user/note pair. The pinned-note list is ordered by `created_at DESC` per user, so the most recently pinned note appears first. Deleting a note cascades through this table; toggling a pin does **not** modify `note.updated_at`.
|
||||
|
||||
## OAuth Session Table
|
||||
|
||||
| **Column Name** | **Data Type** | **Constraints** | **Description** |
|
||||
@@ -750,6 +764,8 @@ erDiagram
|
||||
user ||--o{ skill : "manages"
|
||||
user ||--o{ tool : "manages"
|
||||
user ||--o{ note : "owns"
|
||||
user ||--o{ pinned_note : "pins"
|
||||
note ||--o{ pinned_note : "pinned_by"
|
||||
user ||--|| oauth_session : "has"
|
||||
|
||||
%% Content Relationships
|
||||
@@ -958,10 +974,16 @@ erDiagram
|
||||
text title
|
||||
json data
|
||||
json meta
|
||||
boolean is_pinned
|
||||
json access_control
|
||||
}
|
||||
|
||||
pinned_note {
|
||||
text id PK
|
||||
text user_id FK
|
||||
text note_id FK
|
||||
bigint created_at
|
||||
}
|
||||
|
||||
oauth_session {
|
||||
text id PK
|
||||
text user_id FK
|
||||
|
||||
@@ -205,6 +205,16 @@ is also being used and set to `True`. **Never disable this if OAUTH/SSO is not b
|
||||
- Description: Sets global default metadata (capabilities and other model info) for all models. These defaults act as a baseline — per-model overrides always take precedence. For capabilities, the defaults and per-model values are merged (per-model wins on conflicts). For other metadata fields, the default is only applied if the model has no value set. Configurable via **Admin Settings → Models**.
|
||||
- Persistence: This environment variable is a `PersistentConfig` variable. Stored at config key `models.default_metadata`.
|
||||
|
||||
:::info
|
||||
|
||||
`DEFAULT_MODEL_METADATA` is read from the environment as a JSON string at startup.
|
||||
|
||||
- Use valid JSON (for example: `{"capabilities":{"vision":true,"web_search":true}}`)
|
||||
- If parsing fails, Open WebUI logs the error and falls back to `{}`
|
||||
- The parsed value is then applied even when persistent config is disabled, so configured defaults are honored at startup.
|
||||
|
||||
:::
|
||||
|
||||
#### `DEFAULT_MODEL_PARAMS`
|
||||
|
||||
- Type: `dict` (JSON object)
|
||||
@@ -521,6 +531,12 @@ Enabling `ENABLE_REALTIME_CHAT_SAVE` causes every single token generated by the
|
||||
- Default: `False`
|
||||
- Description: When enabled, uses a hardcoded extension-to-MIME dictionary as a last-resort fallback when both the system MIME database and the file's stored content type metadata fail to determine the content type of an image. This is primarily useful on minimal container images (e.g., wolfi-base) that lack `/etc/mime.types` and have legacy files without stored content type metadata. Supported extensions include `.webp`, `.png`, `.jpg`, `.jpeg`, `.gif`, `.svg`, `.bmp`, `.tiff`, `.ico`, `.heic`, `.heif`, and `.avif`.
|
||||
|
||||
#### `ENABLE_PROFILE_IMAGE_URL_FORWARDING`
|
||||
|
||||
- Type: `bool`
|
||||
- Default: `True`
|
||||
- Description: Controls whether the user and model profile-image endpoints honor an external `http(s)://` URL stored in `profile_image_url` by issuing a `302 Found` redirect to the original origin. When `False`, the redirect is suppressed and the endpoint falls through to the bundled default image instead. Set to `False` to prevent client-side IP, User-Agent, and Referer leaks to attacker-controlled origins via attacker-stored profile URLs (data URIs and same-origin/static images continue to load normally). Existing deployments that legitimately rely on external profile image URLs (e.g. Gravatar redirects served by upstream identity providers) should keep the default. **This variable is read once at startup — it is not a `PersistentConfig` and cannot be changed from the Admin UI.**
|
||||
|
||||
#### `CHAT_RESPONSE_STREAM_DELTA_CHUNK_SIZE`
|
||||
|
||||
- Type: `int`
|
||||
@@ -3619,14 +3635,14 @@ The authentication flow also depends on a browser pop-up window. Please ensure t
|
||||
|
||||
- Type: `bool`
|
||||
- Default: `True`
|
||||
- Description: Controls whether the "Personal OneDrive" option appears in the attachment menu. Requires `ONEDRIVE_PERSONAL_CLIENT_ID` to be configured.
|
||||
- Description: Controls whether the "Personal OneDrive" option appears in the attachment menu. The option is only shown when `ONEDRIVE_CLIENT_ID_PERSONAL` (or the legacy `ONEDRIVE_CLIENT_ID`) is configured — without a client ID, the option stays hidden even if this variable is `True`.
|
||||
- Persistence: This environment variable is a `PersistentConfig` variable.
|
||||
|
||||
#### `ENABLE_ONEDRIVE_BUSINESS`
|
||||
|
||||
- Type: `bool`
|
||||
- Default: `True`
|
||||
- Description: Controls whether the "Work/School OneDrive" option appears in the attachment menu. Requires `ONEDRIVE_CLIENT_ID` to be configured.
|
||||
- Description: Controls whether the "Work/School OneDrive" option appears in the attachment menu. The option is only shown when `ONEDRIVE_CLIENT_ID_BUSINESS` (or the legacy `ONEDRIVE_CLIENT_ID`) is configured — without a client ID, the option stays hidden even if this variable is `True`.
|
||||
- Persistence: This environment variable is a `PersistentConfig` variable.
|
||||
|
||||
#### `ONEDRIVE_CLIENT_ID`
|
||||
@@ -3765,6 +3781,7 @@ Allow only specific domains: WEB_FETCH_FILTER_LIST="example.com,trusted-site.org
|
||||
- `searxng` - Uses the [SearXNG](https://github.com/searxng/searxng) search engine.
|
||||
- `google_pse` - Uses the [Google Programmable Search Engine](https://programmablesearchengine.google.com/about/).
|
||||
- `brave` - Uses the [Brave search engine](https://brave.com/search/api/).
|
||||
- `brave_llm_context` - Uses Brave's [LLM Context API](https://brave.com/search/api/) endpoint (`/res/v1/llm/context`). Returns pre-extracted, relevance-scored page passages instead of short snippets, so Open WebUI skips the post-search scraping step. Same API key and rate limits as `brave`.
|
||||
- `kagi` - Uses the [Kagi](https://www.kagi.com/) search engine.
|
||||
- `mojeek` - Uses the [Mojeek](https://www.mojeek.com/) search engine.
|
||||
- `bocha` - Uses the Bocha search engine.
|
||||
@@ -3839,7 +3856,7 @@ the search query. Example: `http://searxng.local/search?q=<query>`
|
||||
#### `BRAVE_SEARCH_API_KEY`
|
||||
|
||||
- Type: `str`
|
||||
- Description: Sets the API key for the Brave Search API.
|
||||
- Description: Sets the API key for the Brave Search API. Used by both the `brave` and `brave_llm_context` engines.
|
||||
- Persistence: This environment variable is a `PersistentConfig` variable.
|
||||
|
||||
:::info
|
||||
@@ -3848,6 +3865,13 @@ Brave's free tier enforces a rate limit of 1 request per second. Open WebUI auto
|
||||
|
||||
:::
|
||||
|
||||
#### `BRAVE_SEARCH_CONTEXT_TOKENS`
|
||||
|
||||
- Type: `int`
|
||||
- Default: `8192`
|
||||
- Description: Maximum total tokens to retrieve per query when `WEB_SEARCH_ENGINE=brave_llm_context`. Sent to Brave's LLM Context API as `maximum_number_of_tokens`. Valid range is `1024`–`32768`. Higher values pull richer extracted passages at the cost of API quota; lower values keep responses lean. Configurable via **Admin Panel → Settings → Web Search → Context Tokens** (only shown when the `brave_llm_context` engine is selected).
|
||||
- Persistence: This environment variable is a `PersistentConfig` variable. Stored at config key `rag.web.search.brave_search_context_tokens`.
|
||||
|
||||
#### `KAGI_SEARCH_API_KEY`
|
||||
|
||||
- Type: `str`
|
||||
@@ -4392,6 +4416,13 @@ Note: If none of the specified languages are available and `en` was not in your
|
||||
- Description: Comma-separated list of supported audio MIME types for Speech-to-Text (e.g., `audio/wav,audio/mpeg,video/*`). Leave empty to use defaults.
|
||||
- Persistence: This environment variable is a `PersistentConfig` variable.
|
||||
|
||||
#### `AUDIO_STT_ALLOWED_EXTENSIONS`
|
||||
|
||||
- Type: `str`
|
||||
- Default: `mp3,wav,m4a,webm,ogg,flac,mp4,mpga,mpeg`
|
||||
- Description: Comma-separated list of audio file extensions accepted by the Speech-to-Text upload endpoint. Uploads with extensions outside this list are rejected with `400 Invalid audio file extension`. Comparison is case-insensitive. Set to an empty value to skip the extension check (MIME-type validation via `AUDIO_STT_SUPPORTED_CONTENT_TYPES` still applies). Configurable via **Admin Settings → Audio → STT**.
|
||||
- Persistence: This environment variable is a `PersistentConfig` variable.
|
||||
|
||||
### Text-to-Speech
|
||||
|
||||
#### `AUDIO_TTS_API_KEY`
|
||||
@@ -4458,10 +4489,17 @@ Note: If none of the specified languages are available and `en` was not in your
|
||||
|
||||
### Voice Mode
|
||||
|
||||
#### `ENABLE_VOICE_MODE_PROMPT`
|
||||
|
||||
- Type: `bool`
|
||||
- Default: `True`
|
||||
- Description: Master switch for the voice-mode system prompt. When `True`, voice-mode chats prepend either the custom `VOICE_MODE_PROMPT_TEMPLATE` (if set) or `DEFAULT_VOICE_MODE_PROMPT_TEMPLATE`. When `False`, no voice-specific system prompt is injected — the model uses only the regular system prompt and chat history. Configurable in **Admin Settings → Interface → Voice Mode Custom Prompt** (toggle).
|
||||
- Persistence: This environment variable is a `PersistentConfig` variable. Stored at config key `task.voice.prompt.enable`.
|
||||
|
||||
#### `VOICE_MODE_PROMPT_TEMPLATE`
|
||||
- Type: `str`
|
||||
- Default: The value of `DEFAULT_VOICE_MODE_PROMPT_TEMPLATE` environment variable.
|
||||
- Description: Configures a custom system prompt for voice mode interactions. Allows administrators to control how the AI responds in voice conversations (style, length, tone). Leave empty to use the default prompt optimized for voice conversations, or provide custom instructions to tailor the voice assistant's behavior.
|
||||
- Description: Configures a custom system prompt for voice mode interactions. Allows administrators to control how the AI responds in voice conversations (style, length, tone). Leave empty to use the default prompt optimized for voice conversations, or provide custom instructions to tailor the voice assistant's behavior. Only applied when `ENABLE_VOICE_MODE_PROMPT` is `True`.
|
||||
- Persistence: This environment variable is a `PersistentConfig` variable.
|
||||
|
||||
### OpenAI Text-to-Speech
|
||||
@@ -5927,7 +5965,7 @@ For API Key creation (and the API keys themselves):
|
||||
1. This setting controls API key creation permission for non-admin users (directly or via User Groups)
|
||||
2. API keys must also be globally enabled using `ENABLE_API_KEYS`
|
||||
|
||||
**Note:** Administrators can generate API keys whenever `ENABLE_API_KEYS` is enabled, even without `features.api_keys`. See the [Authentication Setup for API Key](/reference/monitoring#authentication-setup-for-api-key-) guide for detailed setup instructions.
|
||||
**Note:** Administrators can generate API keys whenever `ENABLE_API_KEYS` is enabled, even without `features.api_keys`. See the [API Keys](/features/authentication-access/api-keys) guide for detailed setup instructions.
|
||||
|
||||
:::
|
||||
|
||||
@@ -6060,6 +6098,13 @@ These settings control whether users can share workspace items **publicly**.
|
||||
- Description: Enables or disables **public sharing** of notes.
|
||||
- Persistence: This environment variable is a `PersistentConfig` variable.
|
||||
|
||||
#### `USER_PERMISSIONS_CHAT_ALLOW_PUBLIC_SHARING`
|
||||
|
||||
- Type: `bool`
|
||||
- Default: `False`
|
||||
- Description: Enables or disables **public sharing** of chat conversations. When disabled, the access-control selector in the chat share modal hides the "Public" option for non-admin users — they can still create share links scoped to specific users or groups, but cannot make a chat reachable by anyone with the link. Admins always retain the ability to share chats publicly. Requires `USER_PERMISSIONS_CHAT_SHARE` (Share Chat) to be enabled for the user. Configurable per-group in **Admin Panel → Users → Groups → Permissions → Chats Public Sharing**.
|
||||
- Persistence: This environment variable is a `PersistentConfig` variable.
|
||||
|
||||
### Access Grants
|
||||
|
||||
#### `USER_PERMISSIONS_ACCESS_GRANTS_ALLOW_USERS`
|
||||
|
||||
Reference in New Issue
Block a user