# 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 = <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

```sql
-- 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';
```

### Web search

```sql
-- 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

```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 '<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

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