# Open WebUI Configuration via SQLite Open WebUI v0.10+ stores persistent config as JSON key/value pairs in its SQLite database at `/app/backend/data/webui.db` (Docker volume: `/var/lib/docker/volumes/openwebui_data/_data/webui.db`). After the first launch, Docker `ConfigVar` environment variables are **ignored** — the DB is the single source of truth. All configuration changes after initial setup must be made directly in SQLite. ## Schema ```sql CREATE TABLE config ( "key" TEXT NOT NULL, value JSON NOT NULL, updated_at BIGINT, PRIMARY KEY ("key") ); ``` ## Essential config keys ### Model capabilities (vision, image generation) ```sql -- Enable vision for all models (required for image upload in chat) UPDATE config SET value = '{"capabilities": {"vision": true}}', updated_at = WHERE key = 'models.default_metadata'; ``` Without this, image upload is disabled in Open WebUI regardless of which vision-capable models are connected. ### Image generation ```sql -- Enable image generation feature UPDATE config SET value = 'true', updated_at = WHERE key = 'image_generation.enable'; -- Engine/model is stored in separate keys -- Engine: openai, automatic1111, comfyui, gemini UPDATE config SET value = '"openai"', updated_at = WHERE key = 'image_generation.engine'; UPDATE config SET value = '"gemini/imagen-4.0-generate-001"', updated_at = WHERE key = 'image_generation.model'; ``` ### Web search ```sql -- Enable web search UPDATE config SET value = 'true', updated_at = WHERE key = 'web.search.enable'; -- Set engine: searxng, google_pse, brave, serpapi, etc. UPDATE config SET value = '"google_pse"', updated_at = WHERE key = 'web.search.engine'; ``` Public SearXNG instances (searx.be, search.sapti.me, etc.) are heavily rate-limited (403/429). Google PSE is the reliable fallback but uses API quota. Self-hosted SearXNG is ideal. ### MCP tool server connections ```python import sqlite3, json, time db = sqlite3.connect('/var/lib/docker/volumes/openwebui_data/_data/webui.db') row = db.execute("SELECT value FROM config WHERE key='tool_server.connections'").fetchone() current = json.loads(row[0]) if row else [] # Remove duplicates by port current = [c for c in current if '' not in c.get('url', '')] current.append({ 'url': 'http://host.docker.internal:/mcp', 'type': 'mcp', # CRITICAL: not 'openapi' 'auth_type': 'none', 'key': '', 'headers': None, 'config': { 'enable': True, 'function_name_filter_list': '', 'access_grants': [{ 'principal_type': 'group', 'principal_id': '', # from DB: SELECT value FROM config WHERE key='ui.default_group_id' 'permission': 'read' }] }, 'info': {'id': '', 'name': 'Server Name', 'description': 'What it does'} }) db.execute("UPDATE config SET value = ?, updated_at = ? WHERE key = ?", (json.dumps(current), int(time.time()), 'tool_server.connections')) db.commit() db.close() # Then: docker restart openwebui ``` ### User features/permissions ```sql -- Check image_generation and other feature flags SELECT value FROM config WHERE key = 'user.permissions'; -- Look for JSON path: $.features.image_generation ``` ## Change workflow 1. Update the DB: `python3 -c "..."` (sqlite3 write) 2. Verify: `SELECT value FROM config WHERE key = '...'` 3. Restart: `docker restart openwebui` 4. Wait 10-15 seconds for health check ## Pitfalls - **ConfigVar env vars are dead on restart** — After first launch, `DEFAULT_MODEL_METADATA` and other ConfigVar env vars in docker-compose are silently ignored. The DB wins. - **Quoting in shell** — When running Python SQLite updates through SSH, use single-quoted Python strings and double-quoted SQL identifiers to avoid escaping hell. Better: write a .py script, scp it, run it. - **Group ID discovery** — The principal_id for access grants is found at `SELECT value FROM config WHERE key='ui.default_group_id'`. This changes per installation.