Files

4.0 KiB

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

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)

-- Enable vision for all models (required for image upload in chat)
UPDATE config SET value = '{"capabilities": {"vision": true}}', updated_at = <unix_ts>
WHERE key = 'models.default_metadata';

Without this, image upload is disabled in Open WebUI regardless of which vision-capable models are connected.

Image generation

-- Enable image generation feature
UPDATE config SET value = 'true', updated_at = <unix_ts> WHERE key = 'image_generation.enable';
-- Engine/model is stored in separate keys
-- Engine: openai, automatic1111, comfyui, gemini
UPDATE config SET value = '"openai"', updated_at = <unix_ts> WHERE key = 'image_generation.engine';
UPDATE config SET value = '"gemini/imagen-4.0-generate-001"', updated_at = <unix_ts> WHERE key = 'image_generation.model';
-- Enable web search
UPDATE config SET value = 'true', updated_at = <unix_ts> WHERE key = 'web.search.enable';
-- Set engine: searxng, google_pse, brave, serpapi, etc.
UPDATE config SET value = '"google_pse"', updated_at = <unix_ts> 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

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 '<port>' not in c.get('url', '')]

current.append({
    'url': 'http://host.docker.internal:<PORT>/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': '<group-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

-- 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.