Skip to main content

Database Schema

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.

[!WARNING] This documentation reflects schema changes up to Open WebUI v0.11.0.

Open-WebUI Internal SQLite Database

For Open-WebUI, the SQLite database serves as the backbone for user management, chat history, file storage, and various other core functionalities. Understanding this structure is essential for anyone looking to contribute to or maintain the project effectively.

Internal SQLite Location

You can find the SQLite database at root -> data -> webui.db

📁 Root (/)
├── 📁 data
│   ├── 📁 cache
│   ├── 📁 uploads
│   ├── 📁 vector_db
│   └── 📄 webui.db
├── 📄 dev.sh
├── 📁 open_webui
├── 📄 requirements.txt
├── 📄 start.sh
└── 📄 start_windows.bat

Copy Database Locally

If you want to copy the Open-WebUI SQLite database running in the container to your local machine, you can use:

docker cp open-webui:/app/backend/data/webui.db ./webui.db

Alternatively, you can access the database within the container using:

docker exec -it open-webui /bin/sh

Table Overview

Here is a complete list of tables in Open-WebUI's SQLite database. The tables are listed alphabetically and numbered for convenience.

No.Table NameDescription
01access_grantStores normalized access control grants for all resources
02authStores user authentication credentials and login information
03calendarStores user-owned calendars with access control
04calendar_eventStores calendar events with recurrence (RRULE) support
05calendar_event_attendeeTracks attendee RSVPs for shared calendar events
06channelManages chat channels and their configurations
07channel_fileLinks files to channels and messages
08channel_memberTracks user membership and permissions within channels
09chatStores chat sessions and their metadata
10chat_fileLinks files to chats and messages
11chatidtagMaps relationships between chats and their associated tags
12configMaintains system-wide configuration settings
13documentLegacy. Pre-Knowledge documents table; data migrated to knowledge and no longer used (see note below)
14feedbackCaptures user feedback and ratings
15fileManages uploaded files and their metadata
16folderOrganizes files and content into hierarchical structures
17functionStores custom functions and their configurations
18groupManages user groups and their permissions
19group_memberTracks user membership within groups
20knowledgeStores knowledge base entries and related information
21knowledge_fileLinks files to knowledge bases
22memoryMaintains chat history and context memory
23messageStores individual chat messages and their content
24message_reactionRecords user reactions (emojis/responses) to messages
25migrate_historyTracks database schema version and migration records
26modelManages AI model configurations and settings
27noteStores user-created notes and annotations
28oauth_sessionManages active OAuth sessions for users
29promptStores templates and configurations for AI prompts
30prompt_historyTracks version history and snapshots for prompts
31shared_chatStores snapshots of shared chats for link sharing
32skillStores reusable markdown instruction sets (Skills)
33tagManages tags/labels for content categorization
34toolStores configurations for system tools and integrations
35userMaintains user profiles and account information
36automationStores user-defined scheduled automations
37automation_runStores execution history for automation runs
38pinned_noteTracks per-user note pins (each row = one user pinning one note)
39chat_messageNormalized per-message store for chat conversations

Note: there are two additional tables in Open-WebUI's SQLite database that are not related to Open-WebUI's core functionality, that have been excluded:

  • Alembic Version table
  • Migrate History table

Note on the document table: it is a legacy table from before the Knowledge feature. Its rows were migrated into the knowledge table (migration 6a39f3d8e55c) and nothing writes to it anymore, but no migration drops it, so it may still be present (empty) in databases that predate the Knowledge feature. There is no backing model for it in current code.

Now that we have all the tables, let's understand the structure of each table.

Access Grant Table

Column NameData TypeConstraintsDescription
idIntegerPRIMARY KEY, AUTOINCREMENTUnique identifier
resource_typeTextNOT NULLType of resource (e.g., model, knowledge, tool)
resource_idTextNOT NULLID of the specific resource
principal_typeTextNOT NULLType of grantee: user, group or anyone
principal_idTextNOT NULLID of the user or group (or * for public)
permissionTextNOT NULLPermission level: read or write
created_atBigIntegernullableGrant creation timestamp

Things to know about the access_grant table:

  • Unique constraint on (resource_type, resource_id, principal_type, principal_id, permission) to prevent duplicate grants
  • Indexed on (resource_type, resource_id) and (principal_type, principal_id) for efficient lookups
  • Replaces the former access_control JSON column that was previously embedded in each resource table
  • principal_type of user with principal_id of * represents public access, meaning every signed-in user. It does not reach visitors who are not logged in
  • principal_type of anyone (added in v0.11.0) is the no-sign-in grant behind open share links. It is only ever stored as anyone / * / read, any other combination is rejected, and it is only honoured for the shared_chat resource type. Every other resource strips it
  • Supports both group-level and individual user-level access grants

Auth Table

Column NameData TypeConstraintsDescription
idStringPRIMARY KEYUnique identifier
emailString-User's email
passwordText-Hashed password
activeBoolean-Account status

Things to know about the auth table:

  • Uses UUID for primary key
  • One-to-One relationship with users table (shared id)

Channel Table

Column NameData TypeConstraintsDescription
idTextPRIMARY KEYUnique identifier (UUID)
user_idText-Owner/creator of channel
typeTextnullableChannel type
nameText-Channel name
descriptionTextnullableChannel description
dataJSONnullableFlexible data storage
metaJSONnullableChannel metadata

| created_at | BigInteger | - | Creation timestamp (nanoseconds) | | updated_at | BigInteger | - | Last update timestamp (nanoseconds) |

Things to know about the auth table:

  • Uses UUID for primary key
  • Case-insensitive channel names (stored lowercase)

Channel Member Table

Column NameData TypeConstraintsDescription
idTEXTNOT NULLUnique identifier for the channel membership
channel_idTEXTNOT NULLReference to the channel
user_idTEXTNOT NULLReference to the user
created_atBIGINT-Timestamp when membership was created

Channel File Table

Column NameData TypeConstraintsDescription
idTextPRIMARY KEYUnique identifier (UUID)
user_idTextNOT NULLOwner of the relationship
channel_idTextFOREIGN KEY(channel.id), NOT NULLReference to the channel
file_idTextFOREIGN KEY(file.id), NOT NULLReference to the file
message_idTextFOREIGN KEY(message.id), nullableReference to associated message
created_atBigIntegerNOT NULLCreation timestamp
updated_atBigIntegerNOT NULLLast update timestamp

Things to know about the channel_file table:

  • Unique constraint on (channel_id, file_id) to prevent duplicate entries
  • Foreign key relationships with CASCADE delete
  • Indexed on channel_id, file_id, and user_id for performance

Chat Table

Column NameData TypeConstraintsDescription
idStringPRIMARY KEYUnique identifier (UUID)
user_idString-Owner of the chat
titleText-Chat title
chatJSON-Chat content and history
created_atBigInteger-Creation timestamp
updated_atBigInteger-Last update timestamp
share_idTextUNIQUE, nullableSharing identifier
archivedBooleandefault=FalseArchive status
pinnedBooleandefault=False, nullablePin status
metaJSONserver_default=""Metadata including tags
folder_idTextnullableParent folder ID
tasksJSONnullableChat-level task/todo list used by agentic workflows
summaryTextnullableOptional chat summary text
last_read_atBigIntegernullableLast read timestamp used for unread indicators
current_message_idTextnullableCurrent (active leaf) message of the chat's history
variablesJSONnullableValues filled in for the model's chat variables

Things to know about the chat table:

  • tasks and summary support structured planning/status UX in chat sessions.
  • last_read_at is used by sidebar unread state logic (compare with updated_at).
  • share_id references the shared_chat.id token when the chat has an active share link.
  • current_message_id was added in v0.11.0 (migration 9a1b2c3d4e5f). It records the chat's current message, the leaf of the active branch that a new reply continues from, and is backfilled from the existing history when the migration runs. Context compaction and context-usage resolution read it so they work on the branch actually in play rather than the whole message tree.
  • variables was added in v0.11.0 (migration c49178636c78). It holds the values a user filled in for the chat variables declared by the model's system prompt, as a flat map keyed by variable name, and is copied along when a chat is forked or cloned. Temporary chats keep their values in the request instead, so nothing is stored.
  • A migration (242a2047eae0) adds an old_chat column (Text) that backs up the original JSON chat blob as text. It is a migration safety net, not part of the active model, and is not read at runtime.

Shared Chat Table

Column NameData TypeConstraintsDescription
idTextPRIMARY KEYShare token (UUID) used in /s/{id} URLs
chat_idTextFOREIGN KEY(chat.id) CASCADE, NOT NULLReference to the original chat
user_idTextNOT NULLUser who created the share
titleTextnullableChat title at time of sharing
chatJSONnullableSnapshot of chat content at share time
created_atBigIntegernullableShare creation timestamp
updated_atBigIntegernullableLast re-snapshot timestamp

Things to know about the shared_chat table:

  • Replaces the previous pattern of storing shared chat snapshots as phantom rows in the chat table with user_id set to shared-{chat_id}.
  • Each row is an immutable snapshot of the original chat at the time of sharing (or last re-share). The snapshot is updated when the user clicks "Update and Copy Link".
  • Deleting the original chat cascades to delete the shared snapshot.
  • Access control for shared chats is managed via the access_grant table with resource_type = 'shared_chat'.

Chat Message Table

The chat_message table is the normalized per-message store for chat conversations: one row per message, separate from the JSON history blob in chat.chat and distinct from the channel Message Table (which holds channel/thread messages, not chat-model turns).

Column NameData TypeConstraintsDescription
idTextPRIMARY KEYUnique identifier (UUID)
chat_idTextFOREIGN KEY(chat.id) CASCADE, NOT NULLParent chat
user_idTextindexedAuthor of the message
roleTextNOT NULLMessage role: user, assistant, or system
parent_idTextnullableParent message id (for branched conversations)
contentJSONnullableMessage content (a string or a list of content blocks)
outputJSONnullableGenerated output payload
model_idTextnullable, indexedModel that produced the message
filesJSONnullableAttached files
sourcesJSONnullableRetrieval/citation sources
embedsJSONnullableEmbedded artifacts
metaJSONnullableMessage metadata; marks internal sub-agent and timer messages (added in v0.11.0)
doneBooleandefault=TrueWhether generation completed
status_historyJSONnullableStreamed status updates during generation
errorJSONnullableError payload when generation failed
usageJSONnullableToken/usage statistics
context_summaryTextnullablePer-message context summary (added in v0.10.0)
created_atBigIntegerindexedCreation timestamp
updated_atBigInteger-Last update timestamp

Things to know about the chat_message table:

  • Deleting a chat cascades to delete its messages (chat_id foreign key with ON DELETE CASCADE).
  • Composite indexes back the common access patterns: (chat_id, parent_id), (model_id, created_at), and (user_id, created_at).
  • context_summary was added in v0.10.0 (migration 4c5ce3d2f27f) to store a summary of the message's context.
  • meta was added in v0.11.0 (migration 856c5b02fb54). It carries per-message metadata and is what marks the messages Open WebUI injects on a user's behalf, such as a sub-agent result or a fired timer, so the interface can render them differently from a message the user typed.

Automation Table

Column NameData TypeConstraintsDescription
idTextPRIMARY KEYUnique identifier (UUID)
user_idTextNOT NULLOwner of the automation
folder_idTextnullableFolder the runs' chats are created in
nameTextNOT NULLAutomation display name
dataJSONNOT NULLAutomation payload (prompt, model_id, rrule, optional terminal config)
metaJSONnullableOptional metadata
is_activeBooleanNOT NULL, default=TrueActive/paused state
last_run_atBigIntegernullableLast execution time
next_run_atBigIntegernullableNext scheduled execution time
created_atBigIntegerNOT NULLCreation timestamp
updated_atBigIntegerNOT NULLLast update timestamp

Things to know about the automation table:

  • next_run_at is indexed for efficient due-run polling.
  • data.rrule defines recurrence and drives scheduler calculations.
  • folder_id was added in v0.11.0 (migration 959eaac8f909) together with a (user_id, folder_id) index, so an owner's automations can be listed per folder. It is not a foreign key: deleting a folder clears the column on that owner's automations instead of deleting the automation, and a run whose folder has disappeared in the meantime clears the column and files its chat outside any folder.

Automation Run Table

Column NameData TypeConstraintsDescription
idTextPRIMARY KEYUnique identifier (UUID)
automation_idTextNOT NULLReference to automation
chat_idTextnullableChat created by this run (if available)
statusTextNOT NULLRun status (success / error)
errorTextnullableError details when status is error
created_atBigIntegerNOT NULLExecution record timestamp

Things to know about the automation_run table:

  • Indexed by automation_id for fast per-automation run history queries.
  • Rows are deleted when an automation is deleted.

Calendar Table

Column NameData TypeConstraintsDescription
idTextPRIMARY KEYUnique identifier (UUID)
user_idTextNOT NULLOwner of the calendar
nameTextNOT NULLCalendar display name
colorTextnullableDisplay color (hex, e.g. #3b82f6)
is_defaultBooleanNOT NULL, default=FalseWhether this is the user's default calendar
dataJSONnullableExtensible data payload
metaJSONnullableOptional metadata
created_atBigIntegerNOT NULLCreation timestamp
updated_atBigIntegerNOT NULLLast update timestamp

Things to know about the calendar table:

  • Indexed on user_id for efficient per-user calendar listing.
  • A default "Personal" calendar is auto-created on first access.
  • The "Scheduled Tasks" calendar is virtual: it is not stored in this table. Instead, the API synthesizes it at response time (with constant ID __scheduled_tasks__) for users who have Automations access. Automation RRULE future runs and past execution records are rendered as virtual events on this calendar.
  • Access control is managed via the access_grant table with resource_type = 'calendar', enabling calendar sharing between users and groups.
  • A user can only delete non-default calendars. Deleting a calendar cascades to all its events, attendees, and access grants.

Calendar Event Table

Column NameData TypeConstraintsDescription
idTextPRIMARY KEYUnique identifier (UUID)
calendar_idTextNOT NULLReference to parent calendar
user_idTextNOT NULLUser who created the event
titleTextNOT NULLEvent title
descriptionTextnullableEvent description
start_atBigIntegerNOT NULLStart time (epoch nanoseconds)
end_atBigIntegernullableEnd time (epoch nanoseconds)
all_dayBooleanNOT NULL, default=FalseWhether this is an all-day event
rruleTextnullableiCalendar RRULE for recurrence
colorTextnullablePer-event color override
locationTextnullableEvent location
dataJSONnullableExtensible data payload
metaJSONnullableOptional metadata (e.g. automation_id)
is_cancelledBooleanNOT NULL, default=FalseSoft-cancel flag
created_atBigIntegerNOT NULLCreation timestamp
updated_atBigIntegerNOT NULLLast update timestamp

Things to know about the calendar_event table:

  • Composite index on (calendar_id, start_at) for efficient range queries within a calendar.
  • Composite index on (user_id, start_at) for efficient per-user date range queries.
  • Recurring events store an rrule string and are expanded into individual instances at query time (server-side Python expansion using dateutil).
  • Cancelled events (is_cancelled = True) are excluded from range queries but retained in the database.

Calendar Event Attendee Table

Column NameData TypeConstraintsDescription
idTextPRIMARY KEYUnique identifier (UUID)
event_idTextNOT NULLReference to the calendar event
user_idTextNOT NULLUser invited to the event
statusTextNOT NULL, default='pending'RSVP status: pending, accepted, declined, tentative
metaJSONnullableOptional metadata
created_atBigIntegerNOT NULLCreation timestamp
updated_atBigIntegerNOT NULLLast update timestamp

Things to know about the calendar_event_attendee table:

  • Unique constraint on (event_id, user_id) to prevent duplicate attendee entries.
  • Indexed on (user_id, status) for efficient lookups of events a user is invited to.
  • Attendees are replaced in bulk when an event is updated with a new attendee list.
  • Deleting an event cascades to delete all attendee records.

Chat File Table

Column NameData TypeConstraintsDescription
idTextPRIMARY KEYUnique identifier (UUID)
user_idTextNOT NULLUser associated with the file
chat_idTextFOREIGN KEY(chat.id), NOT NULLReference to the chat
file_idTextFOREIGN KEY(file.id), NOT NULLReference to the file
message_idTextnullableReference to associated message
created_atBigIntegerNOT NULLCreation timestamp
updated_atBigIntegerNOT NULLLast update timestamp

Things to know about the chat_file table:

  • Unique constraint on (chat_id, file_id) to prevent duplicate entries
  • Foreign key relationships with CASCADE delete
  • Indexed on chat_id, file_id, message_id, and user_id for performance

Why this table was added:

  • Query Efficiency: Before this, files were embedded in message objects. This table allows direct indexed lookups for finding all files in a chat without iterating through every message.
  • Data Consistency: Acts as a single source of truth for file associations. In multi-node deployments, all nodes query this table instead of relying on potentially inconsistent embedded data.
  • Deduplication: The database-level unique constraint prevents duplicate file associations, which is more reliable than application-level checks.

Chat ID Tag Table

Column NameData TypeConstraintsDescription
idVARCHAR(255)NOT NULLUnique identifier
tag_nameVARCHAR(255)NOT NULLName of the tag
chat_idVARCHAR(255)NOT NULLReference to chat
user_idVARCHAR(255)NOT NULLReference to user
timestampINTEGERNOT NULLCreation timestamp

Config

As of v0.10.0 the config table is per-key: every setting is its own row keyed by a dot-notation path, replacing the previous single-row JSON blob.

Column NameData TypeConstraintsDescription
keyTextPRIMARY KEYConfig key in dot notation (e.g. audio.stt.engine)
valueJSONNOT NULLThe stored value for this key
updated_atBigIntegernullableLast update timestamp (epoch)

Things to know about the config table:

  • Reshaped in migration 3ff2c63645b8. The old single-row schema (id INTEGER PK, data JSON, version INTEGER, created_at/updated_at DATETIME) was migrated by exploding the JSON blob into one row per key.
  • The pre-migration table is preserved as config_old (renamed, not dropped) so the change is reversible; it is not used at runtime.
  • Reads/writes go through individual keys, which avoids rewriting the entire configuration blob on every change.

Feedback Table

Column NameData TypeConstraintsDescription
idTextPRIMARY KEYUnique identifier (UUID)
user_idText-User who provided feedback
versionBigIntegerdefault=0Feedback version number
typeText-Type of feedback
dataJSONnullableFeedback data including ratings
metaJSONnullableMetadata (arena, chat_id, etc)
snapshotJSONnullableAssociated chat snapshot
created_atBigInteger-Creation timestamp
updated_atBigInteger-Last update timestamp

File Table

Column NameData TypeConstraintsDescription
idStringPRIMARY KEYUnique identifier
user_idString-Owner of the file
hashTextnullableFile hash/checksum
filenameText-Name of the file
pathTextnullableFile system path
dataJSONnullableFile-related data
metaJSONnullableFile metadata

| created_at | BigInteger | - | Creation timestamp | | updated_at | BigInteger | - | Last update timestamp |

The meta field's expected structure:

{
    "name": string,          # Optional display name
    "content_type": string,  # MIME type
    "size": integer,         # File size in bytes
    # Additional metadata supported via ConfigDict(extra="allow")
}

Folder Table

Column NameData TypeConstraintsDescription
idTextPK (composite)Unique identifier (UUID)
parent_idTextnullableParent folder ID for hierarchy
user_idTextPK (composite)Owner of the folder
nameText-Folder name
itemsJSONnullableFolder contents
dataJSONnullableAdditional folder data
metaJSONnullableFolder metadata
is_expandedBooleandefault=FalseUI expansion state
created_atBigInteger-Creation timestamp
updated_atBigInteger-Last update timestamp

Things to know about the folder table:

  • Primary key is composite (id, user_id)
  • Folders can be nested (parent_id reference)
  • Root folders have null parent_id
  • Folder names must be unique within the same parent

Function Table

Column NameData TypeConstraintsDescription
idStringPRIMARY KEYUnique identifier
user_idString-Owner of the function
nameText-Function name
typeText-Function type
contentText-Function content/code
metaJSON-Function metadata
valvesJSON-Function control settings
is_activeBoolean-Function active status
is_globalBoolean-Global availability flag
created_atBigInteger-Creation timestamp
updated_atBigInteger-Last update timestamp

Things to know about the function table:

  • type is one of: pipe, filter, action, event (the event type was added in v0.10.0). The type is auto-detected from the top-level class name in the function's source code.

Group Table

Column NameData TypeConstraintsDescription
idTextPRIMARY KEY, UNIQUEUnique identifier (UUID)
user_idText-Group owner/creator
nameText-Group name
descriptionText-Group description
dataJSONnullableAdditional group data
metaJSONnullableGroup metadata
permissionsJSONnullablePermission configuration
created_atBigInteger-Creation timestamp
updated_atBigInteger-Last update timestamp

Note: The user_ids column has been migrated to the group_member table.

Group Member Table

Column NameData TypeConstraintsDescription
idTextPRIMARY KEY, UNIQUEUnique identifier (UUID)
group_idTextFOREIGN KEY(group.id), NOT NULLReference to the group
user_idTextFOREIGN KEY(user.id), NOT NULLReference to the user
created_atBigIntegernullableCreation timestamp
updated_atBigIntegernullableLast update timestamp

Things to know about the group_member table:

  • Unique constraint on (group_id, user_id) to prevent duplicate memberships
  • Foreign key relationships with CASCADE delete to group and user tables

Knowledge Table

Column NameData TypeConstraintsDescription
idTextPRIMARY KEY, UNIQUEUnique identifier (UUID)
user_idText-Knowledge base owner
nameText-Knowledge base name
descriptionText-Knowledge base description
dataJSONnullableKnowledge base content
metaJSONnullableAdditional metadata

| created_at | BigInteger | - | Creation timestamp | | updated_at | BigInteger | - | Last update timestamp |

Knowledge File Table

Column NameData TypeConstraintsDescription
idTextPRIMARY KEYUnique identifier (UUID)
user_idTextNOT NULLOwner of the relationship
knowledge_idTextFOREIGN KEY(knowledge.id), NOT NULLReference to the knowledge base
file_idTextFOREIGN KEY(file.id), NOT NULLReference to the file
created_atBigIntegerNOT NULLCreation timestamp
updated_atBigIntegerNOT NULLLast update timestamp

Things to know about the knowledge_file table:

  • Unique constraint on (knowledge_id, file_id) to prevent duplicate entries
  • Foreign key relationships with CASCADE delete
  • Indexed on knowledge_id, file_id, and user_id for performance

Access control for resources (models, knowledge bases, tools, prompts, notes, files, channels) is managed through the access_grant table rather than embedded JSON. Each grant entry specifies a resource, a principal (user or group), and a permission level (read or write). See the Access Grant Table section above for details.

Memory Table

Column NameData TypeConstraintsDescription
idStringPRIMARY KEYUnique identifier (UUID)
user_idStringindexedMemory owner
typeStringdefault context, indexedMemory type: user or context (added in v0.10.0)
pathTextnullableOptional path for organizing memories into a hierarchy (added in v0.10.0)
contentText-Memory content
metaJSONnullableOptional metadata (added in v0.10.0)
created_atBigInteger-Creation timestamp
updated_atBigInteger-Last update timestamp

Things to know about the memory table:

  • type distinguishes user memories (explicit, user-curated facts) from context memories (learned from conversation); it is indexed for per-type lookups. Added in migration 7b3f2a9c1d4e (with an index fixup migration following it).
  • path and meta (added in a later v0.10.0 migration) back the expanded builtin memory tools, which let the model organize memories under paths and attach structured metadata.
  • A covering index on (id, user_id) was added in v0.11.0 (migration 55f1302ac17c) to speed up per-user memory lookups.

Message Table

Column NameData TypeConstraintsDescription
idTextPRIMARY KEYUnique identifier (UUID)
user_idText-Message author
channel_idTextnullableAssociated channel
parent_idTextnullableParent message for threads
contentText-Message content
dataJSONnullableAdditional message data
metaJSONnullableMessage metadata
created_atBigInteger-Creation timestamp (nanoseconds)
updated_atBigInteger-Last update timestamp (nanoseconds)

Message Reaction Table

Column NameData TypeConstraintsDescription
idTextPRIMARY KEYUnique identifier (UUID)
user_idText-User who reacted
message_idText-Associated message
nameText-Reaction name/emoji
created_atBigInteger-Reaction timestamp

Model Table

Column NameData TypeConstraintsDescription
idTextPRIMARY KEYModel identifier
user_idText-Model owner
base_model_idTextnullableParent model reference
nameText-Display name
paramsJSON-Model parameters
metaJSON-Model metadata

| is_active | Boolean | default=True | Active status | | created_at | BigInteger | - | Creation timestamp | | updated_at | BigInteger | - | Last update timestamp |

Note Table

Column NameData TypeConstraintsDescription
idTextPRIMARY KEYUnique identifier
user_idTextnullableOwner of the note
titleTextnullableNote title
dataJSONnullableNote content and data
metaJSONnullableNote metadata
created_atBigIntegernullableCreation timestamp
updated_atBigIntegernullableLast update timestamp

Pin state is no longer stored on this table. The legacy is_pinned column was removed in migration 4de81c2a3af1 and replaced by a per-user Pinned Note Table. Pre-existing pins were backfilled to the note owner; the API surfaces is_pinned as a per-request join against the calling user's rows.

Pinned Note Table

Per-user note pins. Each row records that one user has pinned one note for their own sidebar. Pinning is private and does not affect any other user with access to the same note.

Column NameData TypeConstraintsDescription
idTextPRIMARY KEYUnique identifier (UUID)
user_idTextNOT NULLThe user who pinned the note
note_idTextNOT NULL, FOREIGN KEY(note.id) ON DELETE CASCADEThe pinned note
created_atBigIntegerNOT NULLPin creation timestamp (used for ordering)

A UNIQUE(user_id, note_id) constraint prevents duplicate pins for the same user/note pair. The pinned-note list is ordered by created_at DESC per user, so the most recently pinned note appears first. Deleting a note cascades through this table; toggling a pin does not modify note.updated_at.

OAuth Session Table

Column NameData TypeConstraintsDescription
idTextPRIMARY KEYUnique session identifier
user_idTextFOREIGN KEY(user.id)Associated user
providerText-OAuth provider (e.g., 'google')
tokenText-OAuth session token
expires_atBigInteger-Token expiration timestamp
created_atBigInteger-Session creation timestamp
updated_atBigInteger-Session last update timestamp

Prompt Table

Column NameData TypeConstraintsDescription
idTextPRIMARY KEYUnique identifier (UUID)
commandStringUNIQUE, INDEXUnique command identifier
user_idStringNOT NULLOwner of the prompt
nameTextNOT NULLDisplay name of the prompt
contentTextNOT NULLPrompt content/template
dataJSONnullableAdditional prompt data
metaJSONnullablePrompt metadata

| is_active | Boolean | default=True | Active status | | version_id | Text | nullable | Current version identifier | | tags | JSON | nullable | Associated tags | | created_at | BigInteger | NOT NULL | Creation timestamp | | updated_at | BigInteger | NOT NULL | Last update timestamp |

Prompt History Table

Column NameData TypeConstraintsDescription
idTextPRIMARY KEYUnique identifier (UUID)
prompt_idTextFOREIGN KEY(prompt.id), INDEXReference to the prompt
parent_idTextnullableReference to the parent version
snapshotJSONNOT NULLSnapshot of the prompt at version
user_idTextNOT NULLUser who created the version
commit_messageTextnullableVersion commit message
created_atBigIntegerNOT NULLCreation timestamp

Skill Table

Column NameData TypeConstraintsDescription
idTextPRIMARY KEYUnique identifier (UUID)
user_idTextNOT NULLOwner/creator of the skill
nameTextNOT NULLDisplay name of the skill
descriptionTextnullableShort description (used in manifest)
contentTextNOT NULLFull skill instructions (Markdown)
dataJSONnullableAdditional skill data
metaJSONnullableSkill metadata
is_activeBooleandefault=TrueActive status
created_atBigIntegerNOT NULLCreation timestamp
updated_atBigIntegerNOT NULLLast update timestamp

Things to know about the skill table:

  • Uses UUID for primary key
  • Access control is managed through the access_grant table (resource_type skill)
  • description is injected into the system prompt as part of the manifest; content is loaded on-demand via the view_skill builtin tool

Tag Table

Column NameData TypeConstraintsDescription
idStringPK (composite)Normalized tag identifier
nameString-Display name
user_idStringPK (composite)Tag owner
metaJSONnullableTag metadata

Things to know about the tag table:

  • Primary key is composite (id, user_id)

Tool Table

Column NameData TypeConstraintsDescription
idStringPRIMARY KEYUnique identifier
user_idString-Tool owner
nameText-Tool name
contentText-Tool content/code
specsJSON-Tool specifications
metaJSON-Tool metadata
valvesJSON-Tool control settings

| created_at | BigInteger | - | Creation timestamp | | updated_at | BigInteger | - | Last update timestamp |

User Table

Column NameData TypeConstraintsDescription
idStringPRIMARY KEYUnique identifier
usernameString(50)nullableUser's unique username
nameString-User's name
emailString-User's email
roleString-User's role
profile_image_urlText-Profile image path
bioTextnullableUser's biography
genderTextnullableUser's gender
date_of_birthDatenullableUser's date of birth
last_active_atBigInteger-Last activity timestamp
updated_atBigInteger-Last update timestamp
created_atBigInteger-Creation timestamp
api_keyStringUNIQUE, nullableAPI authentication key
settingsJSONnullableUser preferences
infoJSONnullableAdditional user info
variablesJSONnullableUser variables substituted into system prompts
oauth_subTextUNIQUEOAuth subject identifier
scimJSONnullableSCIM provisioning data

Things to know about the user table:

  • Uses UUID for primary key
  • One-to-One relationship with auth table (shared id)
  • One-to-One relationship with oauth_session table (via user_id foreign key)
  • email is unique case-insensitively, enforced by the partial unique index uq_user_email_lower on lower(email) where email is not null (migration f0bd01a18a3d). An upgrade onto a database that already holds two accounts differing only in capitalisation stops and names them rather than choosing between them; see Duplicate Emails.
  • variables was added in v0.11.0 (migration b0018471bbbe). It holds the user's own user variables as a flat map of string keys to string values, substituted into system prompts at request time. It is excluded from user API responses and is read through its own endpoints instead.

The scim field's expected structure:

{
    "<provider>": {
        "external_id": string,  # externalId from the identity provider
    },
    # Multiple providers can be stored simultaneously
    # Example:
    # "microsoft": { "external_id": "abc-123" },
    # "okta": { "external_id": "def-456" }
}

Why this column was added:

  • SCIM account linking: Stores per-provider externalId values from SCIM provisioning, enabling identity providers (like Azure AD, Okta) to match users by their external identifiers rather than relying solely on email.
  • Multi-provider support: The per-provider key structure allows a single user to be provisioned from multiple identity providers simultaneously, each storing their own externalId.
  • OAuth fallback: When looking up a user by externalId, the system falls back to matching against oauth_sub if no scim entry is found, enabling seamless linking of SCIM-provisioned and OAuth-authenticated accounts.

Entity Relationship Diagram

To help visualize the relationship between the tables, refer to the below Entity Relationship Diagram (ERD) generated with Mermaid.


Database Encryption with SQLCipher

For enhanced security, Open WebUI supports at-rest encryption for its primary SQLite database using SQLCipher. This is recommended for deployments handling sensitive data where using a larger database like PostgreSQL is not needed.

Prerequisites

SQLCipher encryption requires additional dependencies that are not included by default. Before using this feature, you must install:

  • The SQLCipher system library (e.g., libsqlcipher-dev on Debian/Ubuntu, sqlcipher on macOS via Homebrew)
  • The sqlcipher3-wheels Python package (pip install sqlcipher3-wheels)

For Docker users, this means building a custom image with these dependencies included.

Configuration

To enable encryption, set the following environment variables:

# Required: Set the database type to use SQLCipher
DATABASE_TYPE=sqlite+sqlcipher

# Required: Set a secure password for database encryption
DATABASE_PASSWORD=your-secure-password

When these are set and a full DATABASE_URL is not explicitly defined, Open WebUI will automatically create and use an encrypted database file at ./data/webui.db.

Important Notes

danger
  • The DATABASE_PASSWORD environment variable is required when using sqlite+sqlcipher.
  • The DATABASE_TYPE variable tells Open WebUI which connection logic to use. Setting it to sqlite+sqlcipher activates the encryption feature.
  • Keep the password secure, as it is needed to decrypt and access all application data.
  • Losing the password means losing access to all data in the encrypted database.
Migrating Existing Data to SQLCipher

Open WebUI does not support automatic migration from an unencrypted SQLite database to an encrypted SQLCipher database. If you enable SQLCipher on an existing installation, the application will fail to read your existing unencrypted data.

To use SQLCipher with existing data, you must either:

  1. Start fresh: Enable SQLCipher on a new installation and have users export/re-import their chats manually
  2. Manual database migration: Use external SQLite/SQLCipher tools to export data from the unencrypted database and import it into a new encrypted database (advanced users only)
  3. Use filesystem-level encryption: Consider alternatives like LUKS (Linux) or BitLocker (Windows) for at-rest encryption without database-level changes
  4. Switch to PostgreSQL: For multi-user deployments, PostgreSQL with TLS provides encryption in transit and can be combined with encrypted storage
VariableDefaultDescription
DATABASE_TYPENoneSet to sqlite+sqlcipher for encrypted SQLite
DATABASE_PASSWORD-Encryption password (required for SQLCipher)
DATABASE_ENABLE_SQLITE_WALFalseEnable Write-Ahead Logging for better performance
DATABASE_SQLITE_PRAGMA_SYNCHRONOUSNORMALSQLite sync mode (safe with WAL, avoids fsync per txn)
DATABASE_SQLITE_PRAGMA_BUSY_TIMEOUT5000Write-lock wait time in milliseconds
DATABASE_SQLITE_PRAGMA_CACHE_SIZE-65536Page cache size (negative = KiB; ≈ 64 MB)
DATABASE_SQLITE_PRAGMA_TEMP_STOREMEMORYTemp table storage (MEMORY keeps temps in RAM)
DATABASE_SQLITE_PRAGMA_MMAP_SIZE268435456Memory-mapped I/O size in bytes (≈ 256 MB)
DATABASE_SQLITE_PRAGMA_JOURNAL_SIZE_LIMIT67108864Max WAL file size after checkpoint (≈ 64 MB)
DATABASE_POOL_SIZENoneDatabase connection pool size
DATABASE_POOL_TIMEOUT30Pool connection timeout in seconds
DATABASE_POOL_RECYCLE3600Pool connection recycle time in seconds

For more details, see the Environment Variable Configuration documentation.

This content is for informational purposes only and does not constitute a warranty, guarantee, or contractual commitment. Open WebUI is provided "as is." See your license for applicable terms.