From b73d30b6df8b8a9487db07d6978e1bfd13265226 Mon Sep 17 00:00:00 2001
From: G30 <50341825+silentoplayz@users.noreply.github.com>
Date: Wed, 7 Jan 2026 14:50:04 -0500
Subject: [PATCH] feat: implement global memories toggle and permissions
(#20462)
---
backend/open_webui/config.py | 11 ++
backend/open_webui/main.py | 3 +
backend/open_webui/routers/auths.py | 4 +
backend/open_webui/routers/memories.py | 103 +++++++++++++++++-
backend/open_webui/routers/users.py | 1 +
.../components/admin/Settings/General.svelte | 8 ++
.../admin/Users/Groups/Permissions.svelte | 16 +++
src/lib/components/chat/SettingsModal.svelte | 7 ++
src/lib/constants/permissions.ts | 3 +-
src/lib/stores/index.ts | 1 +
10 files changed, 152 insertions(+), 5 deletions(-)
diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py
index 680ecbb86..3fee61f6e 100644
--- a/backend/open_webui/config.py
+++ b/backend/open_webui/config.py
@@ -1470,6 +1470,10 @@ USER_PERMISSIONS_FEATURES_API_KEYS = (
os.environ.get("USER_PERMISSIONS_FEATURES_API_KEYS", "False").lower() == "true"
)
+USER_PERMISSIONS_FEATURES_MEMORIES = (
+ os.environ.get("USER_PERMISSIONS_FEATURES_MEMORIES", "True").lower() == "true"
+)
+
USER_PERMISSIONS_SETTINGS_INTERFACE = (
os.environ.get("USER_PERMISSIONS_SETTINGS_INTERFACE", "True").lower() == "true"
@@ -1533,6 +1537,7 @@ DEFAULT_USER_PERMISSIONS = {
"web_search": USER_PERMISSIONS_FEATURES_WEB_SEARCH,
"image_generation": USER_PERMISSIONS_FEATURES_IMAGE_GENERATION,
"code_interpreter": USER_PERMISSIONS_FEATURES_CODE_INTERPRETER,
+ "memories": USER_PERMISSIONS_FEATURES_MEMORIES,
},
"settings": {
"interface": USER_PERMISSIONS_SETTINGS_INTERFACE,
@@ -2083,6 +2088,12 @@ ENABLE_CODE_INTERPRETER = PersistentConfig(
os.environ.get("ENABLE_CODE_INTERPRETER", "True").lower() == "true",
)
+ENABLE_MEMORIES = PersistentConfig(
+ "ENABLE_MEMORIES",
+ "memories.enable",
+ os.environ.get("ENABLE_MEMORIES", "True").lower() == "true",
+)
+
CODE_INTERPRETER_ENGINE = PersistentConfig(
"CODE_INTERPRETER_ENGINE",
"code_interpreter.engine",
diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py
index 6a1d79e72..2f384ee7d 100644
--- a/backend/open_webui/main.py
+++ b/backend/open_webui/main.py
@@ -145,6 +145,7 @@ from open_webui.config import (
CODE_INTERPRETER_JUPYTER_AUTH_TOKEN,
CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD,
CODE_INTERPRETER_JUPYTER_TIMEOUT,
+ ENABLE_MEMORIES,
# Image
AUTOMATIC1111_API_AUTH,
AUTOMATIC1111_BASE_URL,
@@ -1106,6 +1107,7 @@ app.state.config.CODE_INTERPRETER_JUPYTER_TIMEOUT = CODE_INTERPRETER_JUPYTER_TIM
app.state.config.IMAGE_GENERATION_ENGINE = IMAGE_GENERATION_ENGINE
app.state.config.ENABLE_IMAGE_GENERATION = ENABLE_IMAGE_GENERATION
app.state.config.ENABLE_IMAGE_PROMPT_GENERATION = ENABLE_IMAGE_PROMPT_GENERATION
+app.state.config.ENABLE_MEMORIES = ENABLE_MEMORIES
app.state.config.IMAGE_GENERATION_MODEL = IMAGE_GENERATION_MODEL
app.state.config.IMAGE_SIZE = IMAGE_SIZE
@@ -1935,6 +1937,7 @@ async def get_app_config(request: Request):
"enable_admin_chat_access": ENABLE_ADMIN_CHAT_ACCESS,
"enable_google_drive_integration": app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION,
"enable_onedrive_integration": app.state.config.ENABLE_ONEDRIVE_INTEGRATION,
+ "enable_memories": app.state.config.ENABLE_MEMORIES,
**(
{
"enable_onedrive_personal": ENABLE_ONEDRIVE_PERSONAL,
diff --git a/backend/open_webui/routers/auths.py b/backend/open_webui/routers/auths.py
index fb0c14137..2c6fc6108 100644
--- a/backend/open_webui/routers/auths.py
+++ b/backend/open_webui/routers/auths.py
@@ -991,6 +991,7 @@ async def get_admin_config(request: Request, user=Depends(get_admin_user)):
"ENABLE_FOLDERS": request.app.state.config.ENABLE_FOLDERS,
"FOLDER_MAX_FILE_COUNT": request.app.state.config.FOLDER_MAX_FILE_COUNT,
"ENABLE_CHANNELS": request.app.state.config.ENABLE_CHANNELS,
+ "ENABLE_MEMORIES": request.app.state.config.ENABLE_MEMORIES,
"ENABLE_NOTES": request.app.state.config.ENABLE_NOTES,
"ENABLE_USER_WEBHOOKS": request.app.state.config.ENABLE_USER_WEBHOOKS,
"PENDING_USER_OVERLAY_TITLE": request.app.state.config.PENDING_USER_OVERLAY_TITLE,
@@ -1015,6 +1016,7 @@ class AdminConfig(BaseModel):
ENABLE_FOLDERS: bool
FOLDER_MAX_FILE_COUNT: Optional[int | str] = None
ENABLE_CHANNELS: bool
+ ENABLE_MEMORIES: bool
ENABLE_NOTES: bool
ENABLE_USER_WEBHOOKS: bool
PENDING_USER_OVERLAY_TITLE: Optional[str] = None
@@ -1046,6 +1048,7 @@ async def update_admin_config(
else None
)
request.app.state.config.ENABLE_CHANNELS = form_data.ENABLE_CHANNELS
+ request.app.state.config.ENABLE_MEMORIES = form_data.ENABLE_MEMORIES
request.app.state.config.ENABLE_NOTES = form_data.ENABLE_NOTES
if form_data.DEFAULT_USER_ROLE in ["pending", "user", "admin"]:
@@ -1091,6 +1094,7 @@ async def update_admin_config(
"ENABLE_FOLDERS": request.app.state.config.ENABLE_FOLDERS,
"FOLDER_MAX_FILE_COUNT": request.app.state.config.FOLDER_MAX_FILE_COUNT,
"ENABLE_CHANNELS": request.app.state.config.ENABLE_CHANNELS,
+ "ENABLE_MEMORIES": request.app.state.config.ENABLE_MEMORIES,
"ENABLE_NOTES": request.app.state.config.ENABLE_NOTES,
"ENABLE_USER_WEBHOOKS": request.app.state.config.ENABLE_USER_WEBHOOKS,
"PENDING_USER_OVERLAY_TITLE": request.app.state.config.PENDING_USER_OVERLAY_TITLE,
diff --git a/backend/open_webui/routers/memories.py b/backend/open_webui/routers/memories.py
index ebb628074..6e98247ce 100644
--- a/backend/open_webui/routers/memories.py
+++ b/backend/open_webui/routers/memories.py
@@ -1,4 +1,4 @@
-from fastapi import APIRouter, Depends, HTTPException, Request
+from fastapi import APIRouter, Depends, HTTPException, Request, status
from pydantic import BaseModel
import logging
import asyncio
@@ -10,11 +10,16 @@ from open_webui.utils.auth import get_verified_user
from open_webui.internal.db import get_session
from sqlalchemy.orm import Session
+from open_webui.utils.access_control import has_permission
+from open_webui.constants import ERROR_MESSAGES
+
log = logging.getLogger(__name__)
router = APIRouter()
+
+
@router.get("/ef")
async def get_embeddings(request: Request):
return {"result": await request.app.state.EMBEDDING_FUNCTION("hello world")}
@@ -26,7 +31,21 @@ async def get_embeddings(request: Request):
@router.get("/", response_model=list[MemoryModel])
-async def get_memories(user=Depends(get_verified_user), db: Session = Depends(get_session)):
+async def get_memories(
+ request: Request, user=Depends(get_verified_user), db: Session = Depends(get_session)
+):
+ if not request.app.state.config.ENABLE_MEMORIES:
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail=ERROR_MESSAGES.NOT_FOUND,
+ )
+
+ if not has_permission(user.id, "features.memories", request.app.state.config.USER_PERMISSIONS):
+ raise HTTPException(
+ status_code=status.HTTP_403_FORBIDDEN,
+ detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
+ )
+
return Memories.get_memories_by_user_id(user.id, db=db)
@@ -50,6 +69,18 @@ async def add_memory(
user=Depends(get_verified_user),
db: Session = Depends(get_session),
):
+ if not request.app.state.config.ENABLE_MEMORIES:
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail=ERROR_MESSAGES.NOT_FOUND,
+ )
+
+ if not has_permission(user.id, "features.memories", request.app.state.config.USER_PERMISSIONS):
+ raise HTTPException(
+ status_code=status.HTTP_403_FORBIDDEN,
+ detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
+ )
+
memory = Memories.insert_new_memory(user.id, form_data.content, db=db)
vector = await request.app.state.EMBEDDING_FUNCTION(memory.content, user=user)
@@ -83,6 +114,18 @@ class QueryMemoryForm(BaseModel):
async def query_memory(
request: Request, form_data: QueryMemoryForm, user=Depends(get_verified_user), db: Session = Depends(get_session)
):
+ if not request.app.state.config.ENABLE_MEMORIES:
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail=ERROR_MESSAGES.NOT_FOUND,
+ )
+
+ if not has_permission(user.id, "features.memories", request.app.state.config.USER_PERMISSIONS):
+ raise HTTPException(
+ status_code=status.HTTP_403_FORBIDDEN,
+ detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
+ )
+
memories = Memories.get_memories_by_user_id(user.id, db=db)
if not memories:
raise HTTPException(status_code=404, detail="No memories found for user")
@@ -105,6 +148,18 @@ async def query_memory(
async def reset_memory_from_vector_db(
request: Request, user=Depends(get_verified_user), db: Session = Depends(get_session)
):
+ if not request.app.state.config.ENABLE_MEMORIES:
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail=ERROR_MESSAGES.NOT_FOUND,
+ )
+
+ if not has_permission(user.id, "features.memories", request.app.state.config.USER_PERMISSIONS):
+ raise HTTPException(
+ status_code=status.HTTP_403_FORBIDDEN,
+ detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
+ )
+
VECTOR_DB_CLIENT.delete_collection(f"user-memory-{user.id}")
memories = Memories.get_memories_by_user_id(user.id, db=db)
@@ -142,7 +197,21 @@ async def reset_memory_from_vector_db(
@router.delete("/delete/user", response_model=bool)
-async def delete_memory_by_user_id(user=Depends(get_verified_user), db: Session = Depends(get_session)):
+async def delete_memory_by_user_id(
+ request: Request, user=Depends(get_verified_user), db: Session = Depends(get_session)
+):
+ if not request.app.state.config.ENABLE_MEMORIES:
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail=ERROR_MESSAGES.NOT_FOUND,
+ )
+
+ if not has_permission(user.id, "features.memories", request.app.state.config.USER_PERMISSIONS):
+ raise HTTPException(
+ status_code=status.HTTP_403_FORBIDDEN,
+ detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
+ )
+
result = Memories.delete_memories_by_user_id(user.id, db=db)
if result:
@@ -168,6 +237,18 @@ async def update_memory_by_id(
user=Depends(get_verified_user),
db: Session = Depends(get_session),
):
+ if not request.app.state.config.ENABLE_MEMORIES:
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail=ERROR_MESSAGES.NOT_FOUND,
+ )
+
+ if not has_permission(user.id, "features.memories", request.app.state.config.USER_PERMISSIONS):
+ raise HTTPException(
+ status_code=status.HTTP_403_FORBIDDEN,
+ detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
+ )
+
memory = Memories.update_memory_by_id_and_user_id(
memory_id, user.id, form_data.content, db=db
)
@@ -201,7 +282,21 @@ async def update_memory_by_id(
@router.delete("/{memory_id}", response_model=bool)
-async def delete_memory_by_id(memory_id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
+async def delete_memory_by_id(
+ memory_id: str, request: Request, user=Depends(get_verified_user), db: Session = Depends(get_session)
+):
+ if not request.app.state.config.ENABLE_MEMORIES:
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail=ERROR_MESSAGES.NOT_FOUND,
+ )
+
+ if not has_permission(user.id, "features.memories", request.app.state.config.USER_PERMISSIONS):
+ raise HTTPException(
+ status_code=status.HTTP_403_FORBIDDEN,
+ detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
+ )
+
result = Memories.delete_memory_by_id_and_user_id(memory_id, user.id, db=db)
if result:
diff --git a/backend/open_webui/routers/users.py b/backend/open_webui/routers/users.py
index 3c3d22c61..f10448458 100644
--- a/backend/open_webui/routers/users.py
+++ b/backend/open_webui/routers/users.py
@@ -226,6 +226,7 @@ class FeaturesPermissions(BaseModel):
web_search: bool = True
image_generation: bool = True
code_interpreter: bool = True
+ memories: bool = True
class SettingsPermissions(BaseModel):
diff --git a/src/lib/components/admin/Settings/General.svelte b/src/lib/components/admin/Settings/General.svelte
index 0b56de20e..c32793741 100644
--- a/src/lib/components/admin/Settings/General.svelte
+++ b/src/lib/components/admin/Settings/General.svelte
@@ -741,6 +741,14 @@