Database Schema
Tables, columns, and relationships in the Quackback database.
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_atcolumns - Full-text search: Posts have a generated
search_vectorcolumn for PostgreSQL full-text search - Relational queries: Drizzle relations enable type-safe joins and nested queries
Tip:
When working with the database, always import from@/lib/dbrather than@quackback/dbdirectly. This ensures proper connection handling.
Table Categories
Category | Tables |
|---|---|
user, session, account, verification, one_time_token, member, invitation, settings | |
boards, posts, comments, votes, tags, post_tags, roadmaps, post_roadmaps | |
post_statuses | |
post_edit_history, comment_edit_history, post_notes, comment_reactions | |
integrations, integration_platform_credentials, integration_event_mappings, post_external_links | |
in_app_notifications, post_subscriptions, notification_preferences, unsubscribe_tokens | |
changelog_entries, changelog_entry_posts | |
post_sentiment, merge_suggestions | |
feedback_sources, raw_feedback_items, feedback_signals, feedback_suggestions, feedback_signal_corrections, external_user_mappings | |
segments, user_segments, user_attribute_definitions | |
api_keys, webhooks |
Authentication Tables
user
User identities for the application. Managed by Better Auth with custom extensions.
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (user) | PRIMARY KEY | Unique user identifier |
| text | NOT NULL | Display name |
| text | NOT NULL, UNIQUE | Email address |
| boolean | NOT NULL, DEFAULT false | Email verification status |
| text | Profile image URL | |
| text | S3 storage key for profile image | |
| text | General user metadata (JSON) | |
| timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
| timestamptz | NOT NULL, DEFAULT now() | Last update timestamp |
Indexes:
user_email_idx(unique) onemail
Relations:
- Has many: sessions, accounts, members, invitations
session
User authentication sessions.
Column | Type | Constraints | Description |
|---|---|---|---|
| text | PRIMARY KEY | Session identifier (Better Auth generated) |
| timestamptz | NOT NULL | Session expiration time |
| text | NOT NULL, UNIQUE | Session token |
| text | Client IP address | |
| text | Client user agent | |
| TypeID (user) | NOT NULL, FK -> user.id ON DELETE CASCADE | Associated user |
| timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
| timestamptz | NOT NULL | Last update timestamp |
Indexes:
session_userId_idxonuser_id
Relations:
- Belongs to: user
account
OAuth and authentication provider accounts.
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (account) | PRIMARY KEY | Account identifier |
| text | NOT NULL | External account ID |
| text | NOT NULL | Auth provider (google, github, etc.) |
| TypeID (user) | NOT NULL, FK -> user.id ON DELETE CASCADE | Associated user |
| text | OAuth access token | |
| text | OAuth refresh token | |
| text | OAuth ID token | |
| timestamptz | Access token expiration | |
| timestamptz | Refresh token expiration | |
| text | OAuth scopes | |
| text | Hashed password (for password auth) | |
| timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
| timestamptz | NOT NULL | Last update timestamp |
Indexes:
account_userId_idxonuser_id
Relations:
- Belongs to: user
verification
Email and other verification tokens.
Column | Type | Constraints | Description |
|---|---|---|---|
| text | PRIMARY KEY | Verification identifier |
| text | NOT NULL | Target identifier (email, etc.) |
| text | NOT NULL | Verification code/token |
| timestamptz | NOT NULL | Expiration time |
| timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
| timestamptz | NOT NULL, DEFAULT now() | Last update timestamp |
Indexes:
verification_identifier_idxonidentifier
one_time_token
Secure cross-domain session transfer tokens (used during workspace provisioning).
Column | Type | Constraints | Description |
|---|---|---|---|
| text | PRIMARY KEY | Token identifier |
| text | NOT NULL | One-time token value |
| TypeID (user) | NOT NULL, FK -> user.id ON DELETE CASCADE | Associated user |
| timestamptz | NOT NULL | Expiration time |
| timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
Relations:
- Belongs to: user
member
Unified membership records linking users to workspace roles.
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (member) | PRIMARY KEY | Member identifier |
| TypeID (user) | NOT NULL, FK -> user.id ON DELETE CASCADE, UNIQUE | Associated user |
| text | NOT NULL, DEFAULT 'member' | Role: 'admin', 'member', or 'user' |
| timestamptz | NOT NULL | Creation timestamp |
Role Descriptions:
admin: Full administrative access, can manage settings and teammember: Team member access, can manage feedbackuser: Portal user access only, can vote/comment on public portal
Indexes:
member_user_idx(unique) onuser_idmember_role_idxonrole
Relations:
- Belongs to: user
- Has many: posts (as author), posts (as owner), comments, votes, post_subscriptions
invitation
Team member invitations.
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (invite) | PRIMARY KEY | Invitation identifier |
| text | NOT NULL | Invitee email address |
| text | Invitee name | |
| text | Assigned role on acceptance | |
| text | NOT NULL, DEFAULT 'pending' | Status: pending, accepted, expired |
| timestamptz | NOT NULL | Expiration time |
| timestamptz | Last email sent timestamp | |
| TypeID (user) | NOT NULL, FK -> user.id ON DELETE CASCADE | User who sent invitation |
| timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
Indexes:
invitation_email_idxonemailinvitation_email_status_idxon(email, status)
Relations:
- Belongs to: user (inviter)
settings
Application settings and branding configuration. Single row in self-hosted deployments.
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (workspace) | PRIMARY KEY | Settings identifier |
| text | NOT NULL | Workspace/application name |
| text | NOT NULL, UNIQUE | URL-safe identifier |
| text | S3 storage key for logo image | |
| text | S3 storage key for favicon | |
| text | S3 storage key for header logo (horizontal wordmark) | |
| text | DEFAULT 'logo_and_name' | Header style: 'logo_and_name', 'logo_only', 'custom_logo' |
| text | Custom header display name | |
| text | Team auth settings (JSON) | |
| text | Portal feature settings (JSON) | |
| text | Theme/branding settings (JSON) | |
| text | Custom CSS for portal | |
| text | Developer settings (JSON), e.g. MCP config | |
| text | Onboarding state tracking (JSON) | |
| text | Additional metadata (JSON) | |
| timestamptz | NOT NULL | Creation 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.
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (board) | PRIMARY KEY | Board identifier |
| text | NOT NULL, UNIQUE | URL-safe identifier |
| text | NOT NULL | Display name |
| text | Board description | |
| boolean | NOT NULL, DEFAULT true | Public visibility |
| jsonb | NOT NULL, DEFAULT | Board-specific settings |
| timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
| timestamptz | NOT NULL, DEFAULT now() | Last update timestamp |
| timestamptz | Soft delete timestamp |
Indexes:
- Unique constraint on
slug boards_is_public_idxonis_publicboards_deleted_at_idxondeleted_at
Settings Schema:
{
"roadmapStatusIds": ["status_xxx", "status_yyy"]
}Relations:
- Has many: posts
posts
Feedback posts submitted by users or team members.
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (post) | PRIMARY KEY | Post identifier |
| TypeID (board) | NOT NULL, FK -> boards.id ON DELETE CASCADE | Parent board |
| text | NOT NULL | Post title |
| text | NOT NULL | Plain text content |
| jsonb | Rich content (TipTap JSON) | |
| TypeID (member) | NOT NULL, FK -> member.id ON DELETE RESTRICT | Author member |
| TypeID (status) | FK -> post_statuses.id ON DELETE SET NULL | Current status |
| TypeID (member) | FK -> member.id ON DELETE SET NULL | Assigned team member |
| integer | NOT NULL, DEFAULT 0, CHECK >= 0 | Cached vote count |
| integer | NOT NULL, DEFAULT 0, CHECK >= 0 | Cached comment count |
| text | Team response text | |
| TypeID (member) | FK -> member.id ON DELETE SET NULL | Response author |
| timestamptz | Response timestamp | |
| TypeID (comment) | Pinned comment as official response | |
| boolean | NOT NULL, DEFAULT false | Prevent portal users from commenting (team members bypass) |
| text | NOT NULL, DEFAULT 'published' | State: published, pending, spam, archived, closed, deleted |
| TypeID (post) | Canonical post this was merged into | |
| timestamptz | When this post was merged | |
| TypeID (member) | FK -> member.id ON DELETE SET NULL | Who performed the merge |
| tsvector | GENERATED | Full-text search vector |
| vector(1536) | Semantic embedding (AI) | |
| text | Model used for embedding | |
| timestamptz | Embedding generation time | |
| jsonb | AI-generated summary (structured) | |
| text | Model used for summary generation | |
| timestamptz | Summary generation timestamp | |
| integer | Comment count when summary was last generated | |
| timestamptz | Last merge suggestion check timestamp | |
| timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
| timestamptz | NOT NULL, DEFAULT now() | Last update timestamp |
| timestamptz | Soft delete timestamp | |
| TypeID (member) | FK -> member.id ON DELETE SET NULL | Who deleted |
Indexes:
posts_board_id_idxonboard_idposts_status_id_idxonstatus_idposts_member_id_idxonmember_idposts_owner_member_id_idxonowner_member_idposts_created_at_idxoncreated_atposts_vote_count_idxonvote_countposts_board_vote_idxon(board_id, vote_count)posts_board_created_at_idxon(board_id, created_at)posts_board_status_idxon(board_id, status_id)posts_member_created_at_idxon(member_id, created_at)posts_with_status_idxpartial on(status_id, vote_count)WHEREstatus_id IS NOT NULLposts_search_vector_idx(GIN) onsearch_vectorposts_deleted_at_idxondeleted_atposts_board_deleted_at_idxon(board_id, deleted_at)posts_moderation_state_idxonmoderation_stateposts_pinned_comment_id_idxonpinned_comment_idposts_canonical_post_id_idxoncanonical_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.
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (comment) | PRIMARY KEY | Comment identifier |
| TypeID (post) | NOT NULL, FK -> posts.id ON DELETE CASCADE | Parent post |
| TypeID (comment) | Parent comment (for replies) | |
| TypeID (member) | NOT NULL, FK -> member.id ON DELETE RESTRICT | Author member |
| text | NOT NULL | Comment text |
| boolean | NOT NULL, DEFAULT false | Team member flag |
| boolean | NOT NULL, DEFAULT false | Private internal note (visible only to team) |
| TypeID (status) | FK -> post_statuses.id ON DELETE SET NULL | Previous status (for status changes recorded with comments) |
| TypeID (status) | FK -> post_statuses.id ON DELETE SET NULL | New status (for status changes recorded with comments) |
| timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
| timestamptz | Soft delete timestamp |
Indexes:
comments_post_id_idxonpost_idcomments_parent_id_idxonparent_idcomments_member_id_idxonmember_idcomments_created_at_idxoncreated_atcomments_post_created_at_idxon(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.
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (vote) | PRIMARY KEY | Vote identifier |
| TypeID (post) | NOT NULL, FK -> posts.id ON DELETE CASCADE | Voted post |
| TypeID (member) | NOT NULL, FK -> member.id ON DELETE CASCADE | Voting member |
| text | Integration source that created the vote | |
| text | URL to the source ticket/conversation | |
| timestamptz | NOT NULL, DEFAULT now() | Vote timestamp |
| timestamptz | NOT NULL, DEFAULT now() | Last update timestamp |
Indexes:
votes_post_id_idxonpost_idvotes_member_post_idx(unique) on(post_id, member_id)votes_member_id_idxonmember_idvotes_member_created_at_idxon(member_id, created_at)
Relations:
- Belongs to: post, member
tags
Labels for categorizing posts.
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (tag) | PRIMARY KEY | Tag identifier |
| text | NOT NULL, UNIQUE | Tag name |
| text | NOT NULL, DEFAULT '#6b7280' | Hex color code |
| timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
| timestamptz | Soft delete timestamp |
Indexes:
- Unique constraint on
name tags_deleted_at_idxondeleted_at
Relations:
- Has many: post_tags
post_tags
Junction table linking posts to tags (many-to-many).
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (post) | NOT NULL, FK -> posts.id ON DELETE CASCADE | Post reference |
| TypeID (tag) | NOT NULL, FK -> tags.id ON DELETE CASCADE | Tag reference |
Indexes:
post_tags_pk(unique) on(post_id, tag_id)- composite primary keypost_tags_post_id_idxonpost_idpost_tags_tag_id_idxontag_id
Relations:
- Belongs to: post, tag
roadmaps
Public roadmap views for displaying planned work.
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (roadmap) | PRIMARY KEY | Roadmap identifier |
| text | NOT NULL, UNIQUE | URL-safe identifier |
| text | NOT NULL | Display name |
| text | Roadmap description | |
| boolean | NOT NULL, DEFAULT true | Public visibility |
| integer | NOT NULL, DEFAULT 0 | Display order |
| timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
| timestamptz | NOT NULL, DEFAULT now() | Last update timestamp |
| timestamptz | Soft delete timestamp |
Indexes:
- Unique constraint on
slug roadmaps_position_idxonpositionroadmaps_is_public_idxonis_publicroadmaps_deleted_at_idxondeleted_at
Relations:
- Has many: post_roadmaps
post_roadmaps
Junction table linking posts to roadmaps (many-to-many).
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (post) | NOT NULL, FK -> posts.id ON DELETE CASCADE | Post reference |
| TypeID (roadmap) | NOT NULL, FK -> roadmaps.id ON DELETE CASCADE | Roadmap reference |
| integer | NOT NULL, DEFAULT 0 | Position within roadmap |
Indexes:
post_roadmaps_pk(unique) on(post_id, roadmap_id)- composite primary keypost_roadmaps_post_id_idxonpost_idpost_roadmaps_roadmap_id_idxonroadmap_idpost_roadmaps_position_idxon(roadmap_id, position)
Relations:
- Belongs to: post, roadmap
Status & Workflow Tables
post_statuses
Customizable status definitions for tracking post lifecycle.
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (status) | PRIMARY KEY | Status identifier |
| text | NOT NULL | Display name |
| text | NOT NULL, UNIQUE | URL-safe identifier |
| text | NOT NULL, DEFAULT '#6b7280' | Hex color code |
| text | NOT NULL, DEFAULT 'active' | Category: 'active', 'complete', 'closed' |
| integer | NOT NULL, DEFAULT 0 | Display order within category |
| boolean | NOT NULL, DEFAULT false | Show on public roadmap |
| boolean | NOT NULL, DEFAULT false | Default for new posts |
| timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
| timestamptz | Soft 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_idxon(category, position)post_statuses_deleted_at_idxondeleted_at
Relations:
- Has many: posts
Default Statuses:
Name | Slug | Color | Category | Show on Roadmap |
|---|---|---|---|---|
Open | open | #3b82f6 | active | No |
Under Review | under_review | #eab308 | active | No |
Planned | planned | #a855f7 | active | Yes |
In Progress | in_progress | #f97316 | active | Yes |
Complete | complete | #22c55e | complete | Yes |
Closed | closed | #6b7280 | closed | No |
History & Notes Tables
post_edit_history
Audit trail for post edits.
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (post_edit) | PRIMARY KEY | Edit record identifier |
| TypeID (post) | NOT NULL, FK -> posts.id ON DELETE CASCADE | Edited post |
| TypeID (member) | NOT NULL, FK -> member.id ON DELETE SET NULL | Editor |
| text | NOT NULL | Title before edit |
| text | NOT NULL | Content before edit |
| jsonb | Rich content before edit | |
| timestamptz | NOT NULL, DEFAULT now() | Edit timestamp |
Indexes:
post_edit_history_post_id_idxonpost_idpost_edit_history_created_at_idxoncreated_at
Relations:
- Belongs to: post, member (editor)
comment_edit_history
Audit trail for comment edits.
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (comment_edit) | PRIMARY KEY | Edit record identifier |
| TypeID (comment) | NOT NULL, FK -> comments.id ON DELETE CASCADE | Edited comment |
| TypeID (member) | NOT NULL, FK -> member.id ON DELETE SET NULL | Editor |
| text | NOT NULL | Content before edit |
| timestamptz | NOT NULL, DEFAULT now() | Edit timestamp |
Indexes:
comment_edit_history_comment_id_idxoncomment_idcomment_edit_history_created_at_idxoncreated_at
Relations:
- Belongs to: comment, member (editor)
post_notes
Internal staff notes on posts (not visible to public users).
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (note) | PRIMARY KEY | Note identifier |
| TypeID (post) | NOT NULL, FK -> posts.id ON DELETE CASCADE | Associated post |
| TypeID (member) | NOT NULL, FK -> member.id ON DELETE RESTRICT | Note author |
| text | NOT NULL | Note content |
| timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
Indexes:
post_notes_post_id_idxonpost_idpost_notes_member_id_idxonmember_idpost_notes_created_at_idxoncreated_at
Relations:
- Belongs to: post, member (author)
comment_reactions
Emoji reactions on comments.
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (reaction) | PRIMARY KEY | Reaction identifier |
| TypeID (comment) | NOT NULL, FK -> comments.id ON DELETE CASCADE | Reacted comment |
| TypeID (member) | NOT NULL, FK -> member.id ON DELETE CASCADE | Reacting member |
| text | NOT NULL | Emoji character |
| timestamptz | NOT NULL, DEFAULT now() | Reaction timestamp |
Supported Emojis:
- Thumbs up, Heart, Party, Smile, Thinking, Eyes
Indexes:
comment_reactions_comment_id_idxoncomment_idcomment_reactions_member_id_idxonmember_idcomment_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.).
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (integration) | PRIMARY KEY | Integration identifier |
| varchar(50) | NOT NULL, UNIQUE | Type: slack, discord, linear, etc. |
| varchar(20) | NOT NULL, DEFAULT 'pending' | Status: pending, active, error |
| text | Encrypted secrets blob (AES-256-GCM JSON) | |
| jsonb | NOT NULL, DEFAULT | Integration-specific config |
| TypeID (member) | FK -> member.id | Who connected |
| timestamptz | Connection timestamp | |
| timestamptz | Last sync timestamp | |
| text | Last error message | |
| timestamptz | Last error timestamp | |
| integer | NOT NULL, DEFAULT 0, CHECK >= 0 | Consecutive error count |
| timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
| timestamptz | NOT NULL, DEFAULT now() | Last update timestamp |
Indexes:
integration_type_unique(unique) onintegration_typeidx_integrations_type_statuson(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.
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (event_mapping) | PRIMARY KEY | Mapping identifier |
| TypeID (integration) | NOT NULL, FK -> integrations.id ON DELETE CASCADE | Parent integration |
| varchar(100) | NOT NULL | Event: post.created, status.changed, etc. |
| varchar(50) | NOT NULL | Action: send_message, create_issue, etc. |
| jsonb | NOT NULL, DEFAULT | Action configuration |
| jsonb | Event filters | |
| boolean | NOT NULL, DEFAULT true | Mapping enabled |
| timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
| timestamptz | NOT 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_lookupon(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.
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (platform_cred) | PRIMARY KEY | Credential identifier |
| varchar(50) | NOT NULL, UNIQUE | Provider type: slack, teams, linear, etc. |
| text | NOT NULL | Encrypted platform credentials (AES-256-GCM) |
| TypeID (member) | FK -> member.id ON DELETE SET NULL | Who configured |
| timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
| timestamptz | NOT NULL, DEFAULT now() | Last update timestamp |
Indexes:
platform_cred_type_unique(unique) onintegration_type
Relations:
- Belongs to: member (configured_by)
post_external_links
External links between posts and external platform issues (e.g., Linear issues, GitHub issues).
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (linked_entity) | PRIMARY KEY | Link identifier |
| TypeID (post) | NOT NULL, FK -> posts.id ON DELETE CASCADE | Associated post |
| TypeID (integration) | NOT NULL, FK -> integrations.id ON DELETE CASCADE | Associated integration |
| varchar(50) | NOT NULL | Integration type |
| text | NOT NULL | External issue/item ID |
| text | URL to external item | |
| timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
Indexes:
post_external_links_type_external_id(unique) on(integration_type, external_id)post_external_links_post_id_idxonpost_idpost_external_links_type_external_id_idxon(integration_type, external_id)
Relations:
- Belongs to: post, integration
Notification Tables
in_app_notifications
In-app notifications displayed in the UI.
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (notification) | PRIMARY KEY | Notification identifier |
| TypeID (member) | NOT NULL, FK -> member.id ON DELETE CASCADE | Recipient member |
| varchar(50) | NOT NULL | Type: post_status_changed, comment_created |
| varchar(255) | NOT NULL | Notification title |
| text | Notification body | |
| TypeID (post) | FK -> posts.id ON DELETE CASCADE | Related post |
| TypeID (comment) | FK -> comments.id ON DELETE CASCADE | Related comment |
| jsonb | Additional data | |
| timestamptz | When marked as read | |
| timestamptz | When archived | |
| timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
Indexes:
in_app_notifications_member_created_idxon(member_id, created_at)in_app_notifications_member_unread_idxpartial onmember_idWHEREread_at IS NULL AND archived_at IS NULLin_app_notifications_post_idxonpost_id
Relations:
- Belongs to: member, post, comment
post_subscriptions
Tracks which members are subscribed to which posts for notifications.
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (post_sub) | PRIMARY KEY | Subscription identifier |
| TypeID (post) | NOT NULL, FK -> posts.id ON DELETE CASCADE | Subscribed post |
| TypeID (member) | NOT NULL, FK -> member.id ON DELETE CASCADE | Subscribed member |
| varchar(20) | NOT NULL | Reason: author, vote, comment, manual |
| boolean | NOT NULL, DEFAULT true | Receive comment notifications |
| boolean | NOT NULL, DEFAULT true | Receive status change notifications |
| timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
| timestamptz | NOT 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_idxonmember_idpost_subscriptions_post_idxonpost_idpost_subscriptions_post_comments_idxpartial onpost_idWHEREnotify_comments = truepost_subscriptions_post_status_idxpartial onpost_idWHEREnotify_status_changes = true
Relations:
- Belongs to: post, member
notification_preferences
Per-member email notification settings.
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (notif_pref) | PRIMARY KEY | Preference identifier |
| TypeID (member) | NOT NULL, UNIQUE, FK -> member.id ON DELETE CASCADE | Member |
| boolean | NOT NULL, DEFAULT true | Email on status changes |
| boolean | NOT NULL, DEFAULT true | Email on new comments |
| boolean | NOT NULL, DEFAULT false | Mute all emails |
| timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
| timestamptz | NOT 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.
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (unsub_token) | PRIMARY KEY | Token identifier |
| text | NOT NULL, UNIQUE | Token value |
| TypeID (member) | NOT NULL, FK -> member.id ON DELETE CASCADE | Member |
| TypeID (post) | FK -> posts.id ON DELETE CASCADE | Related post (null = global) |
| varchar(30) | NOT NULL | Action: unsubscribe_post, unsubscribe_all, mute_post |
| timestamptz | NOT NULL | Expiration time |
| timestamptz | When token was used | |
| timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
Indexes:
- Unique constraint on
token unsubscribe_tokens_member_idxonmember_id
Relations:
- Belongs to: member, post
Changelog Tables
changelog_entries
Public changelog/release notes entries.
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (changelog) | PRIMARY KEY | Entry identifier |
| text | NOT NULL | Entry title |
| text | NOT NULL | Plain text content |
| jsonb | Rich content (TipTap JSON) | |
| TypeID (member) | FK -> member.id ON DELETE SET NULL | Author member |
| timestamptz | Publication timestamp | |
| timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
| timestamptz | NOT NULL, DEFAULT now() | Last update timestamp |
| timestamptz | Soft delete timestamp |
Indexes:
changelog_published_at_idxonpublished_atchangelog_member_id_idxonmember_idchangelog_deleted_at_idxondeleted_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).
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (changelog) | NOT NULL, FK -> changelog_entries.id ON DELETE CASCADE | Changelog entry |
| TypeID (post) | NOT NULL, FK -> posts.id ON DELETE CASCADE | Linked post |
| timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
Indexes:
changelog_entry_posts_pk(unique) on(changelog_entry_id, post_id)- composite primary keychangelog_entry_posts_changelog_id_idxonchangelog_entry_idchangelog_entry_posts_post_id_idxonpost_id
Relations:
- Belongs to: changelog_entry, post
AI Feature Tables
post_sentiment
AI-generated sentiment analysis results for posts (one-to-one with posts).
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (sentiment) | PRIMARY KEY | Sentiment record identifier |
| TypeID (post) | NOT NULL, UNIQUE, FK -> posts.id ON DELETE CASCADE | Analyzed post |
| text | NOT NULL | Result: positive, neutral, negative |
| real | NOT NULL | Confidence score (0-1) |
| text | NOT NULL | AI model used |
| timestamptz | NOT NULL, DEFAULT now() | Analysis timestamp |
| integer | Input token count | |
| integer | Output token count |
Indexes:
post_sentiment_processed_at_idxonprocessed_atpost_sentiment_sentiment_idxonsentiment
Relations:
- Belongs to: post
API Tables
api_keys
API keys for public REST API authentication.
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (api_key) | PRIMARY KEY | API key identifier |
| varchar(255) | NOT NULL | Human-readable key name |
| varchar(64) | NOT NULL, UNIQUE | SHA-256 hash of the API key |
| varchar(12) | NOT NULL | First 12 chars for identification |
| TypeID (member) | NOT NULL, FK -> member.id ON DELETE CASCADE | Member who created the key |
| timestamptz | Last authentication timestamp | |
| timestamptz | Optional expiration date | |
| timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
| timestamptz | Soft delete / revocation timestamp |
Indexes:
- Unique constraint on
key_hash api_keys_created_by_id_idxoncreated_by_idapi_keys_revoked_at_idxonrevoked_at
Relations:
- Belongs to: member (created_by)
webhooks
Webhook configurations for external event notifications.
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (webhook) | PRIMARY KEY | Webhook identifier |
| TypeID (member) | NOT NULL, FK -> member.id ON DELETE CASCADE | Member who created the webhook |
| text | NOT NULL | HTTPS endpoint URL |
| text | NOT NULL | Encrypted HMAC-SHA256 signing secret |
| text[] | NOT NULL | Event types to trigger |
| text[] | Optional board filter | |
| text | NOT NULL, DEFAULT 'active' | Status: active, disabled |
| integer | NOT NULL, DEFAULT 0 | Consecutive delivery failures |
| text | Last error message | |
| timestamptz | Last trigger timestamp | |
| timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
| timestamptz | NOT NULL, DEFAULT now() | Last update timestamp |
| timestamptz | Soft delete timestamp |
Indexes:
webhooks_status_idxonstatuswebhooks_created_by_id_idxoncreated_by_idwebhooks_deleted_at_idxondeleted_at
Relations:
- Belongs to: member (created_by)
merge_suggestions
AI-generated merge suggestions for duplicate posts.
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (merge_sug) | PRIMARY KEY | Suggestion identifier |
| TypeID (post) | NOT NULL, FK -> posts.id ON DELETE CASCADE | Post suggested as duplicate |
| TypeID (post) | NOT NULL, FK -> posts.id ON DELETE CASCADE | Canonical post to merge into |
| text | NOT NULL, DEFAULT 'pending' | Status: pending, accepted, dismissed |
| real | Vector similarity score | |
| real | Full-text search score | |
| real | Combined hybrid score | |
| real | LLM verification confidence | |
| text | LLM explanation for the match | |
| text | LLM model used for verification | |
| timestamptz | When accepted or dismissed | |
| TypeID (member) | FK -> member.id ON DELETE SET NULL | Who resolved |
| timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
| timestamptz | NOT 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.
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (fb_source) | PRIMARY KEY | Source identifier |
| text | NOT NULL | Source type (e.g. integration name) |
| text | NOT NULL | How feedback is delivered |
| text | NOT NULL | Display name |
| TypeID (integration) | FK -> integrations.id | Linked integration |
| boolean | NOT NULL, DEFAULT true | Active flag |
| jsonb | NOT NULL, DEFAULT | Source configuration |
| text | Encrypted secrets | |
| text | Pagination cursor for polling | |
| timestamptz | Last sync timestamp | |
| timestamptz | Last successful sync | |
| text | Most recent error | |
| integer | NOT NULL, DEFAULT 0 | Consecutive errors |
| timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
| timestamptz | NOT NULL, DEFAULT now() | Last update timestamp |
raw_feedback_items
Unprocessed feedback items from external sources, queued for AI extraction.
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (raw_fb) | PRIMARY KEY | Item identifier |
| TypeID (fb_source) | NOT NULL, FK -> feedback_sources.id | Parent source |
| text | NOT NULL | Source type |
| text | External item ID | |
| text | Deduplication key | |
| text | Link back to source | |
| timestamptz | When created in source system | |
| jsonb | Author information | |
| text | NOT NULL | Raw feedback content |
| jsonb | Additional context metadata | |
| text | NOT NULL, DEFAULT 'pending' | State: pending, processing, done, failed |
| integer | NOT NULL, DEFAULT 0 | Processing attempts |
| text | Last processing error | |
| timestamptz | When processing completed | |
| TypeID (member) | Matched user principal | |
| timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
| timestamptz | NOT 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.
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (segment) | PRIMARY KEY | Segment identifier |
| text | NOT NULL | Display name |
| text | Segment description | |
| text | NOT NULL | Type: manual, dynamic |
| text | Display color | |
| jsonb | Dynamic segment rules | |
| text | Cron schedule for re-evaluation | |
| jsonb | Weighting configuration | |
| timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
| timestamptz | NOT NULL, DEFAULT now() | Last update timestamp |
| timestamptz | Soft delete timestamp |
user_segments
Junction table for user-to-segment membership.
user_attribute_definitions
Admin-defined custom user attributes for enrichment.
Column | Type | Constraints | Description |
|---|---|---|---|
| TypeID (user_attr) | PRIMARY KEY | Attribute identifier |
| text | NOT NULL, UNIQUE | Attribute key |
| text | NOT NULL | Display label |
| text | Attribute description | |
| text | NOT NULL | Data type: text, number, boolean, date, currency |
| text | Currency code (for currency type) | |
| text | Mapping key for integration sync | |
| timestamptz | NOT NULL, DEFAULT now() | Creation timestamp |
| timestamptz | NOT 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_01h455vb4pex5vsknk084sn02qboard_01h455vb4pex5vsknk084sn03rmember_01h455vb4pex5vsknk084sn04s
Type prefixes used:
user,session,account,member,invite,workspace,verification,domain,transfer_tokenboard,post,comment,vote,tag,status,roadmapintegration,platform_cred,event_mapping,linked_entity,sync_lognotification,post_sub,notif_pref,unsub_tokenchangelog,sentiment,note,reactionpost_edit,comment_editapi_key,webhooksubscription,invoice(billing)
Schema Files
The schema is defined in Drizzle ORM format in these files:
File | Tables |
|---|---|
| user, session, account, verification, one_time_token, settings, member, invitation, jwks, oauth_client, oauth_access_token, oauth_refresh_token, oauth_consent |
| boards, roadmaps, tags |
| posts, post_tags, post_roadmaps, votes, comments, comment_reactions, post_edit_history, comment_edit_history, post_notes |
| post_statuses |
| integrations, integration_platform_credentials, integration_event_mappings |
| in_app_notifications, post_subscriptions, notification_preferences, unsubscribe_tokens |
| changelog_entries, changelog_entry_posts |
| post_sentiment |
| merge_suggestions |
| feedback_sources, raw_feedback_items, feedback_signals, feedback_suggestions, feedback_signal_corrections, external_user_mappings |
| segments, user_segments |
| user_attribute_definitions |
| api_keys |
| webhooks |
| post_external_links |
Migrations are located in packages/db/drizzle/.
Was this helpful?
Your feedback shapes what we write next.