Skip to content

Database Schema

This document provides a comprehensive reference for the Quackback database schema. All tables use PostgreSQL with Drizzle ORM for type-safe database access.

Overview

Quackback uses a PostgreSQL database with the following characteristics:

  • TypeIDs: All primary keys use TypeID format (UUID storage with type-prefixed strings in the application layer, e.g., post_01h455vb4pex5vsknk084sn02q)
  • Timestamps: All timestamp columns use timestamp with time zone
  • Soft deletes: Most entity tables support soft deletion via deleted_at columns
  • Full-text search: Posts have a generated search_vector column for PostgreSQL full-text search
  • Relational queries: Drizzle relations enable type-safe joins and nested queries

When working with the database, always import from @/lib/db rather than @quackback/db directly. This ensures proper connection handling.

Table Categories

CategoryTables
Authenticationuser, session, account, verification, one_time_token, member, invitation, settings
Contentboards, posts, comments, votes, tags, post_tags, roadmaps, post_roadmaps
Status & Workflowpost_statuses
History & Notespost_edit_history, comment_edit_history, post_notes, comment_reactions
Integrationsintegrations, integration_platform_credentials, integration_event_mappings, post_external_links
Notificationsin_app_notifications, post_subscriptions, notification_preferences, unsubscribe_tokens
Changelogchangelog_entries, changelog_entry_posts
AI Featurespost_sentiment, merge_suggestions
Feedback Pipelinefeedback_sources, raw_feedback_items, feedback_signals, feedback_suggestions, feedback_signal_corrections, external_user_mappings
Segmentssegments, user_segments, user_attribute_definitions
APIapi_keys, webhooks

Authentication Tables

user

User identities for the application. Managed by Better Auth with custom extensions.

ColumnTypeConstraintsDescription
idTypeID (user)PRIMARY KEYUnique user identifier
nametextNOT NULLDisplay name
emailtextNOT NULL, UNIQUEEmail address
email_verifiedbooleanNOT NULL, DEFAULT falseEmail verification status
imagetextProfile image URL
image_keytextS3 storage key for profile image
metadatatextGeneral user metadata (JSON)
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp
updated_attimestamptzNOT NULL, DEFAULT now()Last update timestamp

Indexes:

  • user_email_idx (unique) on email

Relations:

  • Has many: sessions, accounts, members, invitations

session

User authentication sessions.

ColumnTypeConstraintsDescription
idtextPRIMARY KEYSession identifier (Better Auth generated)
expires_attimestamptzNOT NULLSession expiration time
tokentextNOT NULL, UNIQUESession token
ip_addresstextClient IP address
user_agenttextClient user agent
user_idTypeID (user)NOT NULL, FK -> user.id ON DELETE CASCADEAssociated user
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp
updated_attimestamptzNOT NULLLast update timestamp

Indexes:

  • session_userId_idx on user_id

Relations:

  • Belongs to: user

account

OAuth and authentication provider accounts.

ColumnTypeConstraintsDescription
idTypeID (account)PRIMARY KEYAccount identifier
account_idtextNOT NULLExternal account ID
provider_idtextNOT NULLAuth provider (google, github, etc.)
user_idTypeID (user)NOT NULL, FK -> user.id ON DELETE CASCADEAssociated user
access_tokentextOAuth access token
refresh_tokentextOAuth refresh token
id_tokentextOAuth ID token
access_token_expires_attimestamptzAccess token expiration
refresh_token_expires_attimestamptzRefresh token expiration
scopetextOAuth scopes
passwordtextHashed password (for password auth)
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp
updated_attimestamptzNOT NULLLast update timestamp

Indexes:

  • account_userId_idx on user_id

Relations:

  • Belongs to: user

verification

Email and other verification tokens.

ColumnTypeConstraintsDescription
idtextPRIMARY KEYVerification identifier
identifiertextNOT NULLTarget identifier (email, etc.)
valuetextNOT NULLVerification code/token
expires_attimestamptzNOT NULLExpiration time
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp
updated_attimestamptzNOT NULL, DEFAULT now()Last update timestamp

Indexes:

  • verification_identifier_idx on identifier

one_time_token

Secure cross-domain session transfer tokens (used during workspace provisioning).

ColumnTypeConstraintsDescription
idtextPRIMARY KEYToken identifier
tokentextNOT NULLOne-time token value
user_idTypeID (user)NOT NULL, FK -> user.id ON DELETE CASCADEAssociated user
expires_attimestamptzNOT NULLExpiration time
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp

Relations:

  • Belongs to: user

member

Unified membership records linking users to workspace roles.

ColumnTypeConstraintsDescription
idTypeID (member)PRIMARY KEYMember identifier
user_idTypeID (user)NOT NULL, FK -> user.id ON DELETE CASCADE, UNIQUEAssociated user
roletextNOT NULL, DEFAULT 'member'Role: 'admin', 'member', or 'user'
created_attimestamptzNOT NULLCreation timestamp

Role Descriptions:

  • admin: Full administrative access, can manage settings and team
  • member: Team member access, can manage feedback
  • user: Portal user access only, can vote/comment on public portal

Indexes:

  • member_user_idx (unique) on user_id
  • member_role_idx on role

Relations:

  • Belongs to: user
  • Has many: posts (as author), posts (as owner), comments, votes, post_subscriptions

invitation

Team member invitations.

ColumnTypeConstraintsDescription
idTypeID (invite)PRIMARY KEYInvitation identifier
emailtextNOT NULLInvitee email address
nametextInvitee name
roletextAssigned role on acceptance
statustextNOT NULL, DEFAULT 'pending'Status: pending, accepted, expired
expires_attimestamptzNOT NULLExpiration time
last_sent_attimestamptzLast email sent timestamp
inviter_idTypeID (user)NOT NULL, FK -> user.id ON DELETE CASCADEUser who sent invitation
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp

Indexes:

  • invitation_email_idx on email
  • invitation_email_status_idx on (email, status)

Relations:

  • Belongs to: user (inviter)

settings

Application settings and branding configuration. Single row in self-hosted deployments.

ColumnTypeConstraintsDescription
idTypeID (workspace)PRIMARY KEYSettings identifier
nametextNOT NULLWorkspace/application name
slugtextNOT NULL, UNIQUEURL-safe identifier
logo_keytextS3 storage key for logo image
favicon_keytextS3 storage key for favicon
header_logo_keytextS3 storage key for header logo (horizontal wordmark)
header_display_modetextDEFAULT 'logo_and_name'Header style: 'logo_and_name', 'logo_only', 'custom_logo'
header_display_nametextCustom header display name
auth_configtextTeam auth settings (JSON)
portal_configtextPortal feature settings (JSON)
branding_configtextTheme/branding settings (JSON)
custom_csstextCustom CSS for portal
developer_configtextDeveloper settings (JSON), e.g. MCP config
setup_statetextOnboarding state tracking (JSON)
metadatatextAdditional metadata (JSON)
created_attimestamptzNOT NULLCreation timestamp

JSON Column Schemas:

auth_config:

{
  "oauth": { "google": boolean, "github": boolean, "microsoft": boolean },
  "ssoRequired": boolean,
  "openSignup": boolean
}

portal_config:

{
  "oauth": { "password": boolean, "email": boolean, "google": boolean, "github": boolean },
  "features": { "publicView": boolean, "submissions": boolean, "comments": boolean, "voting": boolean }
}

branding_config:

{
  "preset": string,
  "light": { /* ThemeColors */ },
  "dark": { /* ThemeColors */ }
}

setup_state:

{
  "version": number,
  "steps": { "core": boolean, "workspace": boolean, "boards": boolean },
  "completedAt": "ISO timestamp",
  "source": "cloud" | "self-hosted",
  "useCase": "saas" | "consumer" | "marketplace" | "internal"
}

Content Tables

boards

Feedback boards for organizing posts by topic or product area.

ColumnTypeConstraintsDescription
idTypeID (board)PRIMARY KEYBoard identifier
slugtextNOT NULL, UNIQUEURL-safe identifier
nametextNOT NULLDisplay name
descriptiontextBoard description
is_publicbooleanNOT NULL, DEFAULT truePublic visibility
settingsjsonbNOT NULL, DEFAULT {}Board-specific settings
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp
updated_attimestamptzNOT NULL, DEFAULT now()Last update timestamp
deleted_attimestamptzSoft delete timestamp

Indexes:

  • Unique constraint on slug
  • boards_is_public_idx on is_public
  • boards_deleted_at_idx on deleted_at

Settings Schema:

{
  "roadmapStatusIds": ["status_xxx", "status_yyy"]
}

Relations:

  • Has many: posts

posts

Feedback posts submitted by users or team members.

ColumnTypeConstraintsDescription
idTypeID (post)PRIMARY KEYPost identifier
board_idTypeID (board)NOT NULL, FK -> boards.id ON DELETE CASCADEParent board
titletextNOT NULLPost title
contenttextNOT NULLPlain text content
content_jsonjsonbRich content (TipTap JSON)
member_idTypeID (member)NOT NULL, FK -> member.id ON DELETE RESTRICTAuthor member
status_idTypeID (status)FK -> post_statuses.id ON DELETE SET NULLCurrent status
owner_member_idTypeID (member)FK -> member.id ON DELETE SET NULLAssigned team member
vote_countintegerNOT NULL, DEFAULT 0, CHECK >= 0Cached vote count
comment_countintegerNOT NULL, DEFAULT 0, CHECK >= 0Cached comment count
official_responsetextTeam response text
official_response_member_idTypeID (member)FK -> member.id ON DELETE SET NULLResponse author
official_response_attimestamptzResponse timestamp
pinned_comment_idTypeID (comment)Pinned comment as official response
is_comments_lockedbooleanNOT NULL, DEFAULT falsePrevent portal users from commenting (team members bypass)
moderation_statetextNOT NULL, DEFAULT 'published'State: published, pending, spam, archived, closed, deleted
canonical_post_idTypeID (post)Canonical post this was merged into
merged_attimestamptzWhen this post was merged
merged_by_member_idTypeID (member)FK -> member.id ON DELETE SET NULLWho performed the merge
search_vectortsvectorGENERATEDFull-text search vector
embeddingvector(1536)Semantic embedding (AI)
embedding_modeltextModel used for embedding
embedding_updated_attimestamptzEmbedding generation time
summary_jsonjsonbAI-generated summary (structured)
summary_modeltextModel used for summary generation
summary_updated_attimestamptzSummary generation timestamp
summary_comment_countintegerComment count when summary was last generated
merge_checked_attimestamptzLast merge suggestion check timestamp
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp
updated_attimestamptzNOT NULL, DEFAULT now()Last update timestamp
deleted_attimestamptzSoft delete timestamp
deleted_by_member_idTypeID (member)FK -> member.id ON DELETE SET NULLWho deleted

Indexes:

  • posts_board_id_idx on board_id
  • posts_status_id_idx on status_id
  • posts_member_id_idx on member_id
  • posts_owner_member_id_idx on owner_member_id
  • posts_created_at_idx on created_at
  • posts_vote_count_idx on vote_count
  • posts_board_vote_idx on (board_id, vote_count)
  • posts_board_created_at_idx on (board_id, created_at)
  • posts_board_status_idx on (board_id, status_id)
  • posts_member_created_at_idx on (member_id, created_at)
  • posts_with_status_idx partial on (status_id, vote_count) WHERE status_id IS NOT NULL
  • posts_search_vector_idx (GIN) on search_vector
  • posts_deleted_at_idx on deleted_at
  • posts_board_deleted_at_idx on (board_id, deleted_at)
  • posts_moderation_state_idx on moderation_state
  • posts_pinned_comment_id_idx on pinned_comment_id
  • posts_canonical_post_id_idx on canonical_post_id

Relations:

  • Belongs to: board, post_status, member (author), member (owner), post (canonical, for merges)
  • Has many: votes, comments, post_tags, post_roadmaps, post_notes, post_external_links, merged posts

comments

Comments on feedback posts, supporting nested replies.

ColumnTypeConstraintsDescription
idTypeID (comment)PRIMARY KEYComment identifier
post_idTypeID (post)NOT NULL, FK -> posts.id ON DELETE CASCADEParent post
parent_idTypeID (comment)Parent comment (for replies)
member_idTypeID (member)NOT NULL, FK -> member.id ON DELETE RESTRICTAuthor member
contenttextNOT NULLComment text
is_team_memberbooleanNOT NULL, DEFAULT falseTeam member flag
is_privatebooleanNOT NULL, DEFAULT falsePrivate internal note (visible only to team)
status_change_from_idTypeID (status)FK -> post_statuses.id ON DELETE SET NULLPrevious status (for status changes recorded with comments)
status_change_to_idTypeID (status)FK -> post_statuses.id ON DELETE SET NULLNew status (for status changes recorded with comments)
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp
deleted_attimestamptzSoft delete timestamp

Indexes:

  • comments_post_id_idx on post_id
  • comments_parent_id_idx on parent_id
  • comments_member_id_idx on member_id
  • comments_created_at_idx on created_at
  • comments_post_created_at_idx on (post_id, created_at)

Relations:

  • Belongs to: post, member (author), comment (parent), post_status (statusChangeFrom), post_status (statusChangeTo)
  • Has many: comments (replies), comment_reactions

votes

User votes on posts. Each member can vote once per post.

ColumnTypeConstraintsDescription
idTypeID (vote)PRIMARY KEYVote identifier
post_idTypeID (post)NOT NULL, FK -> posts.id ON DELETE CASCADEVoted post
member_idTypeID (member)NOT NULL, FK -> member.id ON DELETE CASCADEVoting member
source_typetextIntegration source that created the vote
source_external_urltextURL to the source ticket/conversation
created_attimestamptzNOT NULL, DEFAULT now()Vote timestamp
updated_attimestamptzNOT NULL, DEFAULT now()Last update timestamp

Indexes:

  • votes_post_id_idx on post_id
  • votes_member_post_idx (unique) on (post_id, member_id)
  • votes_member_id_idx on member_id
  • votes_member_created_at_idx on (member_id, created_at)

Relations:

  • Belongs to: post, member

tags

Labels for categorizing posts.

ColumnTypeConstraintsDescription
idTypeID (tag)PRIMARY KEYTag identifier
nametextNOT NULL, UNIQUETag name
colortextNOT NULL, DEFAULT '#6b7280'Hex color code
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp
deleted_attimestamptzSoft delete timestamp

Indexes:

  • Unique constraint on name
  • tags_deleted_at_idx on deleted_at

Relations:

  • Has many: post_tags

post_tags

Junction table linking posts to tags (many-to-many).

ColumnTypeConstraintsDescription
post_idTypeID (post)NOT NULL, FK -> posts.id ON DELETE CASCADEPost reference
tag_idTypeID (tag)NOT NULL, FK -> tags.id ON DELETE CASCADETag reference

Indexes:

  • post_tags_pk (unique) on (post_id, tag_id) - composite primary key
  • post_tags_post_id_idx on post_id
  • post_tags_tag_id_idx on tag_id

Relations:

  • Belongs to: post, tag

roadmaps

Public roadmap views for displaying planned work.

ColumnTypeConstraintsDescription
idTypeID (roadmap)PRIMARY KEYRoadmap identifier
slugtextNOT NULL, UNIQUEURL-safe identifier
nametextNOT NULLDisplay name
descriptiontextRoadmap description
is_publicbooleanNOT NULL, DEFAULT truePublic visibility
positionintegerNOT NULL, DEFAULT 0Display order
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp
updated_attimestamptzNOT NULL, DEFAULT now()Last update timestamp
deleted_attimestamptzSoft delete timestamp

Indexes:

  • Unique constraint on slug
  • roadmaps_position_idx on position
  • roadmaps_is_public_idx on is_public
  • roadmaps_deleted_at_idx on deleted_at

Relations:

  • Has many: post_roadmaps

post_roadmaps

Junction table linking posts to roadmaps (many-to-many).

ColumnTypeConstraintsDescription
post_idTypeID (post)NOT NULL, FK -> posts.id ON DELETE CASCADEPost reference
roadmap_idTypeID (roadmap)NOT NULL, FK -> roadmaps.id ON DELETE CASCADERoadmap reference
positionintegerNOT NULL, DEFAULT 0Position within roadmap

Indexes:

  • post_roadmaps_pk (unique) on (post_id, roadmap_id) - composite primary key
  • post_roadmaps_post_id_idx on post_id
  • post_roadmaps_roadmap_id_idx on roadmap_id
  • post_roadmaps_position_idx on (roadmap_id, position)

Relations:

  • Belongs to: post, roadmap

Status & Workflow Tables

post_statuses

Customizable status definitions for tracking post lifecycle.

ColumnTypeConstraintsDescription
idTypeID (status)PRIMARY KEYStatus identifier
nametextNOT NULLDisplay name
slugtextNOT NULL, UNIQUEURL-safe identifier
colortextNOT NULL, DEFAULT '#6b7280'Hex color code
categorytextNOT NULL, DEFAULT 'active'Category: 'active', 'complete', 'closed'
positionintegerNOT NULL, DEFAULT 0Display order within category
show_on_roadmapbooleanNOT NULL, DEFAULT falseShow on public roadmap
is_defaultbooleanNOT NULL, DEFAULT falseDefault for new posts
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp
deleted_attimestamptzSoft delete timestamp

Status Categories:

  • active: Posts currently being worked on (Open, Under Review, Planned, In Progress)
  • complete: Successfully delivered posts (Complete)
  • closed: Posts that won't be implemented (Closed)

Indexes:

  • Unique constraint on slug
  • post_statuses_position_idx on (category, position)
  • post_statuses_deleted_at_idx on deleted_at

Relations:

  • Has many: posts

Default Statuses:

NameSlugColorCategoryShow on Roadmap
Openopen#3b82f6activeNo
Under Reviewunder_review#eab308activeNo
Plannedplanned#a855f7activeYes
In Progressin_progress#f97316activeYes
Completecomplete#22c55ecompleteYes
Closedclosed#6b7280closedNo

History & Notes Tables

post_edit_history

Audit trail for post edits.

ColumnTypeConstraintsDescription
idTypeID (post_edit)PRIMARY KEYEdit record identifier
post_idTypeID (post)NOT NULL, FK -> posts.id ON DELETE CASCADEEdited post
editor_member_idTypeID (member)NOT NULL, FK -> member.id ON DELETE SET NULLEditor
previous_titletextNOT NULLTitle before edit
previous_contenttextNOT NULLContent before edit
previous_content_jsonjsonbRich content before edit
created_attimestamptzNOT NULL, DEFAULT now()Edit timestamp

Indexes:

  • post_edit_history_post_id_idx on post_id
  • post_edit_history_created_at_idx on created_at

Relations:

  • Belongs to: post, member (editor)

comment_edit_history

Audit trail for comment edits.

ColumnTypeConstraintsDescription
idTypeID (comment_edit)PRIMARY KEYEdit record identifier
comment_idTypeID (comment)NOT NULL, FK -> comments.id ON DELETE CASCADEEdited comment
editor_member_idTypeID (member)NOT NULL, FK -> member.id ON DELETE SET NULLEditor
previous_contenttextNOT NULLContent before edit
created_attimestamptzNOT NULL, DEFAULT now()Edit timestamp

Indexes:

  • comment_edit_history_comment_id_idx on comment_id
  • comment_edit_history_created_at_idx on created_at

Relations:

  • Belongs to: comment, member (editor)

post_notes

Internal staff notes on posts (not visible to public users).

ColumnTypeConstraintsDescription
idTypeID (note)PRIMARY KEYNote identifier
post_idTypeID (post)NOT NULL, FK -> posts.id ON DELETE CASCADEAssociated post
member_idTypeID (member)NOT NULL, FK -> member.id ON DELETE RESTRICTNote author
contenttextNOT NULLNote content
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp

Indexes:

  • post_notes_post_id_idx on post_id
  • post_notes_member_id_idx on member_id
  • post_notes_created_at_idx on created_at

Relations:

  • Belongs to: post, member (author)

comment_reactions

Emoji reactions on comments.

ColumnTypeConstraintsDescription
idTypeID (reaction)PRIMARY KEYReaction identifier
comment_idTypeID (comment)NOT NULL, FK -> comments.id ON DELETE CASCADEReacted comment
member_idTypeID (member)NOT NULL, FK -> member.id ON DELETE CASCADEReacting member
emojitextNOT NULLEmoji character
created_attimestamptzNOT NULL, DEFAULT now()Reaction timestamp

Supported Emojis:

  • Thumbs up, Heart, Party, Smile, Thinking, Eyes

Indexes:

  • comment_reactions_comment_id_idx on comment_id
  • comment_reactions_member_id_idx on member_id
  • comment_reactions_unique_idx (unique) on (comment_id, member_id, emoji)

Relations:

  • Belongs to: comment, member

Integration Tables

integrations

Third-party integration configurations (Slack, Discord, etc.).

ColumnTypeConstraintsDescription
idTypeID (integration)PRIMARY KEYIntegration identifier
integration_typevarchar(50)NOT NULL, UNIQUEType: slack, discord, linear, etc.
statusvarchar(20)NOT NULL, DEFAULT 'pending'Status: pending, active, error
secretstextEncrypted secrets blob (AES-256-GCM JSON)
configjsonbNOT NULL, DEFAULT {}Integration-specific config
connected_by_member_idTypeID (member)FK -> member.idWho connected
connected_attimestamptzConnection timestamp
last_sync_attimestamptzLast sync timestamp
last_errortextLast error message
last_error_attimestamptzLast error timestamp
error_countintegerNOT NULL, DEFAULT 0, CHECK >= 0Consecutive error count
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp
updated_attimestamptzNOT NULL, DEFAULT now()Last update timestamp

Indexes:

  • integration_type_unique (unique) on integration_type
  • idx_integrations_type_status on (integration_type, status)

Relations:

  • Belongs to: member (connected_by)
  • Has many: integration_event_mappings, post_external_links

integration_event_mappings

Event-to-action mappings for integrations.

ColumnTypeConstraintsDescription
idTypeID (event_mapping)PRIMARY KEYMapping identifier
integration_idTypeID (integration)NOT NULL, FK -> integrations.id ON DELETE CASCADEParent integration
event_typevarchar(100)NOT NULLEvent: post.created, status.changed, etc.
action_typevarchar(50)NOT NULLAction: send_message, create_issue, etc.
action_configjsonbNOT NULL, DEFAULT {}Action configuration
filtersjsonbEvent filters
enabledbooleanNOT NULL, DEFAULT trueMapping enabled
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp
updated_attimestamptzNOT NULL, DEFAULT now()Last update timestamp

Action Config Schema:

{
  "templateId": "tpl_xxx",
  "message": "New feedback: {{title}}"
}

Filters Schema:

{
  "boardIds": ["board_xxx", "board_yyy"],
  "statusIds": ["status_xxx"]
}

Indexes:

  • mapping_unique (unique) on (integration_id, event_type, action_type)
  • idx_event_mappings_lookup on (integration_id, event_type, enabled)

Relations:

  • Belongs to: integration

integration_platform_credentials

Platform-level OAuth app credentials for integrations. One row per provider. Secrets are AES-256-GCM encrypted.

ColumnTypeConstraintsDescription
idTypeID (platform_cred)PRIMARY KEYCredential identifier
integration_typevarchar(50)NOT NULL, UNIQUEProvider type: slack, teams, linear, etc.
secretstextNOT NULLEncrypted platform credentials (AES-256-GCM)
configured_by_member_idTypeID (member)FK -> member.id ON DELETE SET NULLWho configured
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp
updated_attimestamptzNOT NULL, DEFAULT now()Last update timestamp

Indexes:

  • platform_cred_type_unique (unique) on integration_type

Relations:

  • Belongs to: member (configured_by)

post_external_links

External links between posts and external platform issues (e.g., Linear issues, GitHub issues).

ColumnTypeConstraintsDescription
idTypeID (linked_entity)PRIMARY KEYLink identifier
post_idTypeID (post)NOT NULL, FK -> posts.id ON DELETE CASCADEAssociated post
integration_idTypeID (integration)NOT NULL, FK -> integrations.id ON DELETE CASCADEAssociated integration
integration_typevarchar(50)NOT NULLIntegration type
external_idtextNOT NULLExternal issue/item ID
external_urltextURL to external item
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp

Indexes:

  • post_external_links_type_external_id (unique) on (integration_type, external_id)
  • post_external_links_post_id_idx on post_id
  • post_external_links_type_external_id_idx on (integration_type, external_id)

Relations:

  • Belongs to: post, integration

Notification Tables

in_app_notifications

In-app notifications displayed in the UI.

ColumnTypeConstraintsDescription
idTypeID (notification)PRIMARY KEYNotification identifier
member_idTypeID (member)NOT NULL, FK -> member.id ON DELETE CASCADERecipient member
typevarchar(50)NOT NULLType: post_status_changed, comment_created
titlevarchar(255)NOT NULLNotification title
bodytextNotification body
post_idTypeID (post)FK -> posts.id ON DELETE CASCADERelated post
comment_idTypeID (comment)FK -> comments.id ON DELETE CASCADERelated comment
metadatajsonbAdditional data
read_attimestamptzWhen marked as read
archived_attimestamptzWhen archived
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp

Indexes:

  • in_app_notifications_member_created_idx on (member_id, created_at)
  • in_app_notifications_member_unread_idx partial on member_id WHERE read_at IS NULL AND archived_at IS NULL
  • in_app_notifications_post_idx on post_id

Relations:

  • Belongs to: member, post, comment

post_subscriptions

Tracks which members are subscribed to which posts for notifications.

ColumnTypeConstraintsDescription
idTypeID (post_sub)PRIMARY KEYSubscription identifier
post_idTypeID (post)NOT NULL, FK -> posts.id ON DELETE CASCADESubscribed post
member_idTypeID (member)NOT NULL, FK -> member.id ON DELETE CASCADESubscribed member
reasonvarchar(20)NOT NULLReason: author, vote, comment, manual
notify_commentsbooleanNOT NULL, DEFAULT trueReceive comment notifications
notify_status_changesbooleanNOT NULL, DEFAULT trueReceive status change notifications
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp
updated_attimestamptzNOT NULL, DEFAULT now()Last update timestamp

Notification Levels:

  • All activity: notify_comments=true, notify_status_changes=true
  • Status changes only: notify_comments=false, notify_status_changes=true
  • Unsubscribed: Row deleted

Indexes:

  • post_subscriptions_unique (unique) on (post_id, member_id)
  • post_subscriptions_member_idx on member_id
  • post_subscriptions_post_idx on post_id
  • post_subscriptions_post_comments_idx partial on post_id WHERE notify_comments = true
  • post_subscriptions_post_status_idx partial on post_id WHERE notify_status_changes = true

Relations:

  • Belongs to: post, member

notification_preferences

Per-member email notification settings.

ColumnTypeConstraintsDescription
idTypeID (notif_pref)PRIMARY KEYPreference identifier
member_idTypeID (member)NOT NULL, UNIQUE, FK -> member.id ON DELETE CASCADEMember
email_status_changebooleanNOT NULL, DEFAULT trueEmail on status changes
email_new_commentbooleanNOT NULL, DEFAULT trueEmail on new comments
email_mutedbooleanNOT NULL, DEFAULT falseMute all emails
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp
updated_attimestamptzNOT NULL, DEFAULT now()Last update timestamp

Indexes:

  • Unique constraint on member_id

Relations:

  • Belongs to: member

unsubscribe_tokens

One-time tokens for email unsubscribe links.

ColumnTypeConstraintsDescription
idTypeID (unsub_token)PRIMARY KEYToken identifier
tokentextNOT NULL, UNIQUEToken value
member_idTypeID (member)NOT NULL, FK -> member.id ON DELETE CASCADEMember
post_idTypeID (post)FK -> posts.id ON DELETE CASCADERelated post (null = global)
actionvarchar(30)NOT NULLAction: unsubscribe_post, unsubscribe_all, mute_post
expires_attimestamptzNOT NULLExpiration time
used_attimestamptzWhen token was used
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp

Indexes:

  • Unique constraint on token
  • unsubscribe_tokens_member_idx on member_id

Relations:

  • Belongs to: member, post

Changelog Tables

changelog_entries

Public changelog/release notes entries.

ColumnTypeConstraintsDescription
idTypeID (changelog)PRIMARY KEYEntry identifier
titletextNOT NULLEntry title
contenttextNOT NULLPlain text content
content_jsonjsonbRich content (TipTap JSON)
member_idTypeID (member)FK -> member.id ON DELETE SET NULLAuthor member
published_attimestamptzPublication timestamp
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp
updated_attimestamptzNOT NULL, DEFAULT now()Last update timestamp
deleted_attimestamptzSoft delete timestamp

Indexes:

  • changelog_published_at_idx on published_at
  • changelog_member_id_idx on member_id
  • changelog_deleted_at_idx on deleted_at

Relations:

  • Belongs to: member (author)
  • Has many: changelog_entry_posts

changelog_entry_posts

Junction table linking changelog entries to shipped posts (many-to-many).

ColumnTypeConstraintsDescription
changelog_entry_idTypeID (changelog)NOT NULL, FK -> changelog_entries.id ON DELETE CASCADEChangelog entry
post_idTypeID (post)NOT NULL, FK -> posts.id ON DELETE CASCADELinked post
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp

Indexes:

  • changelog_entry_posts_pk (unique) on (changelog_entry_id, post_id) - composite primary key
  • changelog_entry_posts_changelog_id_idx on changelog_entry_id
  • changelog_entry_posts_post_id_idx on post_id

Relations:

  • Belongs to: changelog_entry, post

AI Feature Tables

post_sentiment

AI-generated sentiment analysis results for posts (one-to-one with posts).

ColumnTypeConstraintsDescription
idTypeID (sentiment)PRIMARY KEYSentiment record identifier
post_idTypeID (post)NOT NULL, UNIQUE, FK -> posts.id ON DELETE CASCADEAnalyzed post
sentimenttextNOT NULLResult: positive, neutral, negative
confidencerealNOT NULLConfidence score (0-1)
modeltextNOT NULLAI model used
processed_attimestamptzNOT NULL, DEFAULT now()Analysis timestamp
input_tokensintegerInput token count
output_tokensintegerOutput token count

Indexes:

  • post_sentiment_processed_at_idx on processed_at
  • post_sentiment_sentiment_idx on sentiment

Relations:

  • Belongs to: post

API Tables

api_keys

API keys for public REST API authentication.

ColumnTypeConstraintsDescription
idTypeID (api_key)PRIMARY KEYAPI key identifier
namevarchar(255)NOT NULLHuman-readable key name
key_hashvarchar(64)NOT NULL, UNIQUESHA-256 hash of the API key
key_prefixvarchar(12)NOT NULLFirst 12 chars for identification
created_by_idTypeID (member)NOT NULL, FK -> member.id ON DELETE CASCADEMember who created the key
last_used_attimestamptzLast authentication timestamp
expires_attimestamptzOptional expiration date
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp
revoked_attimestamptzSoft delete / revocation timestamp

Indexes:

  • Unique constraint on key_hash
  • api_keys_created_by_id_idx on created_by_id
  • api_keys_revoked_at_idx on revoked_at

Relations:

  • Belongs to: member (created_by)

webhooks

Webhook configurations for external event notifications.

ColumnTypeConstraintsDescription
idTypeID (webhook)PRIMARY KEYWebhook identifier
created_by_idTypeID (member)NOT NULL, FK -> member.id ON DELETE CASCADEMember who created the webhook
urltextNOT NULLHTTPS endpoint URL
secrettextNOT NULLEncrypted HMAC-SHA256 signing secret
eventstext[]NOT NULLEvent types to trigger
board_idstext[]Optional board filter
statustextNOT NULL, DEFAULT 'active'Status: active, disabled
failure_countintegerNOT NULL, DEFAULT 0Consecutive delivery failures
last_errortextLast error message
last_triggered_attimestamptzLast trigger timestamp
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp
updated_attimestamptzNOT NULL, DEFAULT now()Last update timestamp
deleted_attimestamptzSoft delete timestamp

Indexes:

  • webhooks_status_idx on status
  • webhooks_created_by_id_idx on created_by_id
  • webhooks_deleted_at_idx on deleted_at

Relations:

  • Belongs to: member (created_by)

merge_suggestions

AI-generated merge suggestions for duplicate posts.

ColumnTypeConstraintsDescription
idTypeID (merge_sug)PRIMARY KEYSuggestion identifier
source_post_idTypeID (post)NOT NULL, FK -> posts.id ON DELETE CASCADEPost suggested as duplicate
target_post_idTypeID (post)NOT NULL, FK -> posts.id ON DELETE CASCADECanonical post to merge into
statustextNOT NULL, DEFAULT 'pending'Status: pending, accepted, dismissed
vector_scorerealVector similarity score
fts_scorerealFull-text search score
hybrid_scorerealCombined hybrid score
llm_confidencerealLLM verification confidence
llm_reasoningtextLLM explanation for the match
llm_modeltextLLM model used for verification
resolved_attimestamptzWhen accepted or dismissed
resolved_by_principal_idTypeID (member)FK -> member.id ON DELETE SET NULLWho resolved
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp
updated_attimestamptzNOT NULL, DEFAULT now()Last update timestamp

Relations:

  • Belongs to: post (source), post (target), member (resolved_by)

Feedback Pipeline Tables

feedback_sources

External feedback source configurations for the ingest pipeline.

ColumnTypeConstraintsDescription
idTypeID (fb_source)PRIMARY KEYSource identifier
source_typetextNOT NULLSource type (e.g. integration name)
delivery_modetextNOT NULLHow feedback is delivered
nametextNOT NULLDisplay name
integration_idTypeID (integration)FK -> integrations.idLinked integration
enabledbooleanNOT NULL, DEFAULT trueActive flag
configjsonbNOT NULL, DEFAULT {}Source configuration
secretstextEncrypted secrets
cursortextPagination cursor for polling
last_synced_attimestamptzLast sync timestamp
last_success_attimestamptzLast successful sync
last_errortextMost recent error
error_countintegerNOT NULL, DEFAULT 0Consecutive errors
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp
updated_attimestamptzNOT NULL, DEFAULT now()Last update timestamp

raw_feedback_items

Unprocessed feedback items from external sources, queued for AI extraction.

ColumnTypeConstraintsDescription
idTypeID (raw_fb)PRIMARY KEYItem identifier
source_idTypeID (fb_source)NOT NULL, FK -> feedback_sources.idParent source
source_typetextNOT NULLSource type
external_idtextExternal item ID
dedupe_keytextDeduplication key
external_urltextLink back to source
source_created_attimestamptzWhen created in source system
authorjsonbAuthor information
contenttextNOT NULLRaw feedback content
context_envelopejsonbAdditional context metadata
processing_statetextNOT NULL, DEFAULT 'pending'State: pending, processing, done, failed
attempt_countintegerNOT NULL, DEFAULT 0Processing attempts
last_errortextLast processing error
processed_attimestamptzWhen processing completed
principal_idTypeID (member)Matched user principal
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp
updated_attimestamptzNOT NULL, DEFAULT now()Last update timestamp

feedback_signals

AI-extracted signals from raw feedback items.


feedback_suggestions

Suggestions generated from AI analysis of feedback signals (e.g. create a new post, merge with existing).


feedback_signal_corrections

User corrections and feedback on AI signal extraction accuracy.


external_user_mappings

Maps external user identifiers to internal Quackback principals for cross-system identity resolution.


Segment Tables

segments

User segments for filtering and analytics. Supports manual and dynamic (rule-based) membership.

ColumnTypeConstraintsDescription
idTypeID (segment)PRIMARY KEYSegment identifier
nametextNOT NULLDisplay name
descriptiontextSegment description
typetextNOT NULLType: manual, dynamic
colortextDisplay color
rulesjsonbDynamic segment rules
evaluation_scheduletextCron schedule for re-evaluation
weight_configjsonbWeighting configuration
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp
updated_attimestamptzNOT NULL, DEFAULT now()Last update timestamp
deleted_attimestamptzSoft delete timestamp

user_segments

Junction table for user-to-segment membership.


user_attribute_definitions

Admin-defined custom user attributes for enrichment.

ColumnTypeConstraintsDescription
idTypeID (user_attr)PRIMARY KEYAttribute identifier
keytextNOT NULL, UNIQUEAttribute key
labeltextNOT NULLDisplay label
descriptiontextAttribute description
typetextNOT NULLData type: text, number, boolean, date, currency
currency_codetextCurrency code (for currency type)
external_keytextMapping key for integration sync
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp
updated_attimestamptzNOT NULL, DEFAULT now()Last update timestamp

Database Triggers

comment_count

The comment_count column on posts is maintained in application code (not a database trigger). It's updated whenever comments are created or deleted.


TypeID Format

All entity IDs use the TypeID format, which combines a type prefix with a UUID:

{type}_{uuid_base32}

Examples:

  • post_01h455vb4pex5vsknk084sn02q
  • board_01h455vb4pex5vsknk084sn03r
  • member_01h455vb4pex5vsknk084sn04s

Type prefixes used:

  • user, session, account, member, invite, workspace, verification, domain, transfer_token
  • board, post, comment, vote, tag, status, roadmap
  • integration, platform_cred, event_mapping, linked_entity, sync_log
  • notification, post_sub, notif_pref, unsub_token
  • changelog, sentiment, note, reaction
  • post_edit, comment_edit
  • api_key, webhook
  • subscription, invoice (billing)

Schema Files

The schema is defined in Drizzle ORM format in these files:

FileTables
packages/db/src/schema/auth.tsuser, session, account, verification, one_time_token, settings, member, invitation, jwks, oauth_client, oauth_access_token, oauth_refresh_token, oauth_consent
packages/db/src/schema/boards.tsboards, roadmaps, tags
packages/db/src/schema/posts.tsposts, post_tags, post_roadmaps, votes, comments, comment_reactions, post_edit_history, comment_edit_history, post_notes
packages/db/src/schema/statuses.tspost_statuses
packages/db/src/schema/integrations.tsintegrations, integration_platform_credentials, integration_event_mappings
packages/db/src/schema/notifications.tsin_app_notifications, post_subscriptions, notification_preferences, unsubscribe_tokens
packages/db/src/schema/changelog.tschangelog_entries, changelog_entry_posts
packages/db/src/schema/sentiment.tspost_sentiment
packages/db/src/schema/merge-suggestions.tsmerge_suggestions
packages/db/src/schema/feedback.tsfeedback_sources, raw_feedback_items, feedback_signals, feedback_suggestions, feedback_signal_corrections, external_user_mappings
packages/db/src/schema/segments.tssegments, user_segments
packages/db/src/schema/user-attributes.tsuser_attribute_definitions
packages/db/src/schema/api-keys.tsapi_keys
packages/db/src/schema/webhooks.tswebhooks
packages/db/src/schema/external-links.tspost_external_links

Migrations are located in packages/db/drizzle/.