Sangriadocs

Data model

The Convex schema grouped by feature area, with the purpose of each table.

The schema lives in convex/schema.ts. Every table is workspace-scoped (most carry a workspaceId), and enums that drive the validators come from convex/shared/enums.ts. Tables are grouped below by feature area.

Identity

TablePurpose
usersAuth account + profile (display name, title, timezone, …). isBot: true marks the synthetic user backing an integration.
membersWorkspace membership: role, joinedAt, and deactivatedAt (soft-delete — the row stays so past messages keep an author).
user_statusPer-user online/away/offline presence within a workspace.

Workspace

TablePurpose
workspacesThe tenant: name, joinCode, description, icon, and userId (the primary owner).
workspace_permissionsPer-workspace policy layered on the role matrix (who can create channels, edit-window, etc.).
workspace_channel_settingsAdmin channel-naming policy: allowed name prefixes and max length.

Channels

TablePurpose
channelsA channel: name, isPrivate, isDefault, description, topic.
channel_membersMembership + per-channel settings: role, notificationPreference (all / mentions / none), isMuted, mutedUntil, isActive.

Direct messages

TablePurpose
conversationsA DM or group DM: isGroup, optional name and topic.
conversation_membersWho is in each conversation; carries the per-member isMuted flag.

Messages

TablePurpose
messagesThe message: body (a Quill delta), searchText (plain-text projection for search), attachments, channelId or conversationId, parentMessageId (threads), systemGenerated, forwardedFrom, hiddenUnfurls, and huddle fields.
reactionsAn emoji reaction on a message (value).
pinned_messagesA message pinned in a channel/conversation (channel + conversation ids denormalized from the message).
saved_messagesA "Later" item: a saved message, a message reminder, or a standalone reminder (status, remindAt, note).
draftsAuto-saved unsent drafts, one per target (channel / DM / thread).
scheduled_messages"Send later" messages, delivered by a cron at scheduledFor.
link_previewsCached OpenGraph preview data for external URLs, keyed by url.

Notifications

TablePurpose
notificationsAn in-app notification row for a user (type, content, isRead).
notification_eventsThe fan-out outbox — one row per triggering message/reaction; a worker drains it. See Notifications pipeline.
notification_preferencesPer-user, per-workspace preferences: categories, quiet hours (+ timezone), and highlightWords.
push_subscriptionsOne web-push endpoint per device for a user in a workspace.

Presence & status

TablePurpose
workspace_statusesThe workspace's list of preset custom statuses.
users_workspace_statusA user's current custom status in a workspace (text, emoji value, optional expiresAt).

User groups

TablePurpose
user_groupsA named, @-mentionable group of members (unique handle per workspace).
user_group_membersGroup membership join table.
user_group_channelsChannels a group's members are auto-joined to.

Custom emoji

TablePurpose
custom_emojiWorkspace custom emoji, referenced as :name: in messages and reactions.

Integrations

TablePurpose
appsA workspace integration: botUserId + botMemberId (its bot identity), apiToken, eventUrl, signingSecret, and subscribed eventTypes.
app_webhooksOne incoming webhook = one app posting to one channel, authenticated by a per-channel token.
TablePurpose
sidebar_sectionsA member's custom sidebar sections (personal, per-member).
sidebar_section_itemsWhich channel/conversation a member filed into a section, and its order.
users_workspace_preferencesPer-member workspace preferences (e.g. theme).

Huddles

TablePurpose
huddlesA live huddle: LiveKit roomId, participants, and invited users.
huddle_membersCurrent membership of a live huddle.
huddle_historyA persisted record of each huddle after it ends (live rows are deleted on teardown).

Security & analytics

TablePurpose
login_eventsPer-user sign-in history: method, success, device, ip, location.
audit_logsAppend-only security/admin actions; actor and target names denormalized point-in-time.
analytics_dailyDaily per-workspace metric rollups (one row per workspace × date × metric).
totp_credentialsOne per user: TOTP secret, hashed recovery codes, lockout counter, replay guard.
webauthn_credentialsOne per registered passkey (passwordless sign-in): public key, signature counter, transports, label.
webauthn_challengesShort-TTL passkey registration challenges, keyed by user; consumed on verify, swept by cron.
webauthn_login_challengesShort-TTL passkey sign-in challenges (usernameless, so keyed by the challenge value, not a user); consumed by verifyLogin.
twofa_session_stateMarks an auth session as having cleared the 2FA gate (via TOTP, or created by a passkey sign-in).
account_reauth_stateShort-lived per-session "step-up" marker: the session re-confirmed a password / TOTP just before a sensitive change (adding a passkey).

Conventions across the schema

  • Soft-delete — a removed member is kept (members.deactivatedAt), and a left channel keeps its row (channel_members.isActive: false), so history still renders with an author.
  • Denormalized join tables — membership (channel_members, conversation_members, user_group_members) is normalized so "my channels" is an indexed lookup instead of a scan, avoiding N+1 reads.
  • Search projectionmessages.searchText is a plain-text projection of the Quill body, so full-text search doesn't parse deltas at query time.
  • Enums drive validators — a fixed set of values (roles, statuses, event kinds) is declared once in convex/shared/enums.ts as both a validator and a type; the schema imports the validator. See Conventions.

On this page