Skip to content
Open

Dev #1251

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/features/authentication-access/rbac/groups.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,18 @@ For example, granting the "Marketing" group read access and a specific editor us

* **Read**: Users can view and use the resource.
* **Write**: Users can update or delete the resource.

### Previewing Access (Audit)

When access grants span many groups and resources, it's easy to lose track of who can see what. Open WebUI ships an admin-only **Preview Access** view that resolves every access grant for a specific user or group and lists the result in one place — no need to crawl through individual resource pages.

**For a user** — In **Admin Panel > Users**, hover over a non-admin user row and click the eye-style **Preview Access** button. The modal shows every model, knowledge base, and tool the user can read, aggregated across all of their group memberships and any direct user grants.

**For a group** — In **Admin Panel > Users > Groups**, open the group editor and use the **Preview Group Access** panel. The output is the same shape (models, knowledge, tools), scoped to just that group's grants.

Both views are admin-only and read-only — they reflect what the access-grant table currently says without modifying it. Use them after a permission change to confirm the result matches intent, or as part of a periodic RBAC audit.

Programmatic equivalents:

- `GET /api/v1/users/{user_id}/preview` — user view (admin auth required)
- `GET /api/v1/groups/id/{id}/preview` — group view (admin auth required)
5 changes: 5 additions & 0 deletions docs/features/channels/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,14 @@ Mentioning a model in a channel runs through the same chat-completion pipeline a
| **User tools and MCP tools** | Whatever the model is configured to call, it can call |
| **Filters** | Inlet/outlet/stream filters apply just like in chats |
| **Knowledge (RAG)** | Knowledge bases attached to the model are queried and injected |
| **Attached documents** | Images **and** non-image files (PDF, DOCX, etc.) uploaded in the thread are forwarded into the model's context |

In other words, a channel-summoned model is a fully-equipped agent — not a one-shot completion.

:::note Document attachments in channels (v0.9.6+)
Before v0.9.6, tagging a model in a channel only forwarded **images** from the thread — uploaded PDFs, DOCX, and other non-image documents were ignored, so summarization and document-comparison prompts silently had nothing to work with. As of v0.9.6 these files are forwarded the same way they are in a direct chat, so document workflows behave identically in channels.
:::

### Tagging people and linking channels

Use `@username` to notify teammates. Use `#channel-name` to create clickable cross-references between conversations.
Expand Down
93 changes: 93 additions & 0 deletions docs/features/chat-conversations/web-search/providers/linkup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
---
sidebar_position: 23
title: "Linkup"
---

:::warning

This tutorial is a community contribution and is not supported by the Open WebUI team. It serves only as a demonstration on how to customize Open WebUI for your specific use case. Want to contribute? Check out the [contributing tutorial](https://docs.openwebui.com/contributing).

:::

:::tip

For a comprehensive list of all environment variables related to Web Search (including concurrency settings, result counts, and more), please refer to the [Environment Configuration documentation](/reference/env-configuration#web-search).

:::

:::tip Troubleshooting

Having issues with web search? Check out the [Web Search Troubleshooting Guide](/troubleshooting/web-search) for solutions to common problems like proxy configuration, connection timeouts, and empty content.

:::

## Overview

[Linkup](https://www.linkup.so/) is a search API built for AI applications. Integrating it with Open WebUI lets your language model perform real-time web searches and ground responses in current sources. This tutorial guides you through configuring Linkup as a web search provider.

Linkup support was added in Open WebUI v0.9.6.

## Prerequisites

Ensure you have:

- **Open WebUI Installed**: A running instance of Open WebUI (local or Docker). See the [Getting Started guide](https://docs.openwebui.com/getting-started).
- **Linkup Account**: An account with an API key from [Linkup](https://www.linkup.so/).
- **Admin Access**: Administrative access to your Open WebUI instance.
- **Internet Connection**: Required for Linkup API requests.

## Step-by-Step Configuration

### 1. Obtain a Linkup API Key

1. Log in or sign up at [Linkup](https://www.linkup.so/).
2. Open the API keys section of your dashboard.
3. Copy or generate a new API key. Keep it secure.

### 2. Configure Open WebUI

1. Log in to Open WebUI with an admin account.
2. Open **Admin Panel → Settings → Web Search**.
3. Enable **Web Search** by toggling it **On**.
4. Select **linkup** from the **Web Search Engine** dropdown.
5. Paste your Linkup API key into the **Linkup API Key** field.
6. (Optional) Set the **Search Depth** and **Output Type** (see below).
7. Save your settings.

### 3. Test the Integration

1. Start a chat session in Open WebUI.
2. Click the **plus (+)** button in the prompt field to enable web search.
3. Enter a query (e.g., `+latest AI news`) and confirm Linkup returns real-time results.

## Search Parameters

Linkup requests are built from a small set of defaults that you can override. The query (`q`) and result count (`maxResults`) are injected automatically and cannot be overridden.

| Parameter | Default | Notes |
|-----------|---------|-------|
| `depth` | `standard` | `standard` is faster and cheaper; `deep` runs a more thorough multi-step search. |
| `outputType` | `sourcedAnswer` | `sourcedAnswer` returns an answer plus its source pages; `searchResults` returns raw result entries. |
| `url` | `https://api.linkup.so/v1/search` | Override only if you need to point at a different endpoint. |

These map to the [`LINKUP_SEARCH_PARAMS`](/reference/env-configuration#linkup_search_params) environment variable, supplied as a JSON object. For example:

```bash
-e LINKUP_API_KEY="your_linkup_api_key"
-e LINKUP_SEARCH_PARAMS='{"depth": "deep", "outputType": "searchResults"}'
```

The same fields are exposed in the Admin UI when the `linkup` engine is selected, so you do not need environment variables unless you prefer to manage configuration that way. See [Environment Variable Configuration](https://docs.openwebui.com/environment) for details and the [`ENABLE_PERSISTENT_CONFIG`](/reference/env-configuration#enable_persistent_config) behavior.

## Troubleshooting

- **Invalid API Key**: Ensure the key is copied correctly, without extra spaces.
- **No Results**: Confirm the web search toggle (`+`) is enabled and your internet is active. Try `depth: deep` for sparse topics.
- **Quota Exceeded**: Check your plan and usage on the Linkup dashboard.
- **Settings Not Saved**: Verify admin privileges and that `webui.db` is writable.

## Additional Resources

- [Linkup Documentation](https://docs.linkup.so/): API reference and advanced options.
- [Open WebUI Features](https://docs.openwebui.com/features): Details on RAG and web search.
- [Contributing to Open WebUI](https://docs.openwebui.com/contributing): Share improvements or report issues.
6 changes: 6 additions & 0 deletions docs/features/extensibility/mcp.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,17 @@ Both MCP and OpenAPI tool-server connections accept a free-form **Headers** fiel
| :--- | :--- |
| `{{USER_ID}}` | The calling user's ID. |
| `{{USER_NAME}}` | The calling user's display name. |
| `{{USER_EMAIL}}` | The calling user's email address. |
| `{{USER_ROLE}}` | The calling user's role (e.g. `admin`, `user`). |
| `{{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.

:::note
`{{USER_EMAIL}}` and `{{USER_ROLE}}` were added in v0.9.6. The same release also fixed MCP server connections, where custom-header templates were previously stored but **not** interpolated at request time — they now expand the same way they always have for direct connections and OpenAPI tool servers.
:::

### Function Name Filter List

This field restricts which tools are exposed to the LLM.
Expand Down
2 changes: 1 addition & 1 deletion docs/features/extensibility/pipelines/pipes.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ yield {"choices": [{"delta": {}, "finish_reason": "stop"}]}

This is the single biggest gotcha when building an agent pipeline (LangChain, LlamaIndex, a custom planner, anything that executes its own tools and streams the result back).

`delta.tool_calls` in a chunk means **"please execute this tool call for me, client"**. When Open WebUI's middleware sees it, the tool executor picks up the call, runs it, appends a `role: "tool"` message, and fires a continuation request back at the same pipeline. It does this in a loop capped by `CHAT_RESPONSE_MAX_TOOL_CALL_RETRIES` (≈30).
`delta.tool_calls` in a chunk means **"please execute this tool call for me, client"**. When Open WebUI's middleware sees it, the tool executor picks up the call, runs it, appends a `role: "tool"` message, and fires a continuation request back at the same pipeline. It does this in a loop capped by [`CHAT_RESPONSE_MAX_TOOL_CALL_ITERATIONS`](/reference/env-configuration#chat_response_max_tool_call_iterations) (default 256; `CHAT_RESPONSE_MAX_TOOL_CALL_RETRIES`, default 30, on versions before v0.9.6).

If your pipeline already executed the tool internally, emitting `delta.tool_calls` makes Open WebUI try to execute it *again* — and since the pipeline keeps emitting the same call on every retry, you get 30 copies of the response stacked on top of each other before the retry cap trips. Same thing happens if you set `finish_reason: "tool_calls"` mid-stream.

Expand Down
11 changes: 11 additions & 0 deletions docs/features/extensibility/plugin/development/events.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,17 @@ When Open WebUI calls your external tool (with header forwarding enabled), it in

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

:::warning Open WebUI does **not** forward user credentials to external tools
The `X-OpenWebUI-User-*` and `X-Open-WebUI-Chat-Id` / `X-Open-WebUI-Message-Id` headers forwarded to your tool are **identification only** — they carry no API key or session token. The same applies to MCP custom-header template tokens (`{{USER_ID}}`, `{{USER_NAME}}`, `{{USER_EMAIL}}`, `{{USER_ROLE}}`, `{{CHAT_ID}}`, `{{MESSAGE_ID}}`): there is no `{{API_KEY}}` or `{{TOKEN}}` placeholder, and the user's own API key / session is never sent to the tool server.

So an external tool **must hold its own statically-configured Open WebUI API key** to call this endpoint. The endpoint's authorization check requires the caller to be the chat's owner **or an admin**, which gives you two practical options:

- **Per-user key (uncommon)** — the tool server holds the specific user's API key. Only works for a single-user setup; impractical for a shared MCP server.
- **Admin / service-account key (recommended)** — provision a dedicated admin (or service-account) user in Open WebUI, generate an API key for it, and use that key from the tool server. An admin key works for any user's chat, so a single key serves all callers; the forwarded `X-Open-WebUI-Chat-Id` + `X-Open-WebUI-Message-Id` headers tell your tool *which* chat/message to post to.

Store the key as a secret on the tool server (env var, secrets manager, etc.); do not expect Open WebUI to push it for you.
:::

**Request Body:**

```json
Expand Down
64 changes: 37 additions & 27 deletions docs/features/extensibility/plugin/development/rich-ui.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,28 @@ To embed HTML content, your tool should return an `HTMLResponse` with the `Conte
```python
from fastapi.responses import HTMLResponse

def create_visualization_tool(self, data: str) -> HTMLResponse:
def render_checklist(self, items: list[str]) -> HTMLResponse:
"""
Creates an interactive data visualization that embeds in the chat.
Renders an interactive checklist that embeds in the chat.

:param data: The data to visualize
:param items: The items to show in the checklist
"""
html_content = """
items_html = "".join(
f'<li><label><input type="checkbox"> {item}</label></li>' for item in items
)
html_content = f"""
<!DOCTYPE html>
<html>
<head>
<title>Data Visualization</title>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<title>Checklist</title>
<style>
body {{ font-family: system-ui, sans-serif; padding: 1rem; }}
ul {{ list-style: none; padding: 0; }}
li {{ padding: 0.25rem 0; }}
</style>
</head>
<body>
<div id="chart" style="width:100%;height:400px;"></div>
<script>
// Your interactive chart code here
Plotly.newPlot('chart', [{
y: [1, 2, 3, 4],
type: 'scatter'
}]);
</script>
<ul>{items_html}</ul>
</body>
</html>
"""
Expand All @@ -55,28 +55,28 @@ To provide the LLM with actionable context about the embed, return a **tuple** o
```python
from fastapi.responses import HTMLResponse

def create_chart(self, data: str) -> tuple:
def render_feedback_form(self, prompt: str) -> tuple:
"""
Creates an interactive chart and returns context to the LLM.
Renders an interactive feedback form and returns context to the LLM.

:param data: The data to chart
:param prompt: The question to show the user above the form
"""
html_content = "<html>...</html>"
headers = {"Content-Disposition": "inline"}

# The LLM receives this context instead of the generic message
result_context = {
"status": "success",
"chart_type": "scatter",
"data_points": 42,
"description": "Scatter plot showing correlation between X and Y"
"form_type": "feedback",
"fields": ["rating", "comment"],
"description": f"Rendered a feedback form asking: {prompt!r}"
}

return HTMLResponse(content=html_content, headers=headers), result_context
```

The context can be:
- A **string** — sent as-is to the LLM (e.g., `"Generated a bar chart with 5 categories"`)
- A **string** — sent as-is to the LLM (e.g., `"Rendered a 5-item checklist"`)
- A **dict** — serialized as JSON for structured context
- A **list** — serialized as JSON for multiple items

Expand Down Expand Up @@ -293,22 +293,32 @@ The parent responds with `{ type: 'payload', requestId: ..., payload: ... }` con

### Tool Args Injection (Tools Only)

When a **Tool** returns a Rich UI embed, the tool call arguments (the parameters the model passed to the tool) are automatically injected into the iframe's `window.args`. This allows your embedded HTML to access the tool's input:
When a **Tool** method returns a Rich UI embed inline at the tool-call display (i.e. you return an `HTMLResponse`, or a `(HTMLResponse, context)` tuple, from the tool method itself), the arguments the model passed are exposed on the iframe as `window.args` — **as a JSON string**, not a parsed object. Parse it before use:

```html
<script>
window.addEventListener('load', () => {
// window.args contains the JSON arguments the model passed to this tool
const args = window.args;
if (args) {
const raw = window.args; // JSON string, or undefined
if (raw) {
const args = JSON.parse(raw); // parse to object
document.getElementById('output').textContent = JSON.stringify(args, null, 2);
} else {
console.warn('window.args not set — see Requirements below.');
}
});
</script>
```

:::note
This only works for Tool embeds rendered via the tool call display. Action embeds do not have `window.args` since they are triggered by the user, not the model.
:::warning Requires `allowSameOrigin` — otherwise `window.args` is silently `undefined`
The args are injected from the parent page via `iframe.contentWindow.args = ...`, which the browser blocks under same-origin policy unless the iframe sandbox carries `allow-same-origin`. That is gated by the per-user **Settings → Interface → "iframe Sandbox Allow Same Origin"** toggle, which is **off by default**. If `window.args` comes back undefined and you have not changed this setting, that is the cause: turn it on and reload. See [allowSameOrigin](#allowsameorigin) above for the security trade-off.
:::

:::note Where `window.args` is set, and where it is not
- ✅ **Tool method returning `HTMLResponse` or `(HTMLResponse, context)` tuple** — rendered inline at the "View Result from..." tool call indicator. `window.args` is injected (subject to the `allowSameOrigin` requirement above).
- ❌ **`__event_emitter__({"type": "embeds", "data": {"embeds": [...]}})`** — rendered through the chat-controls Embeds panel, which does not wire `args` at all. `window.args` will always be undefined here, regardless of sandbox settings. This is by design: the embeds-event path has no tool call attached, so there are no args to inject.
- ❌ **Action embeds** — triggered by the user, not the model, so there are no model-supplied args to inject.

If you need to pass dynamic data into an embed rendered via either of the ❌ paths, use the [Payload Requests](#payload-requests) pattern above instead.
:::

### Auto-Injected Libraries
Expand Down
Loading
Loading