Files
formbricks/packages/database/schema.prisma
T
Johannes ecaa2887b7 refactor: align connector enum with formbricks_survey
Rename connector type usage from formbricks to formbricks_survey across Prisma schema, shared types, and connector service logic to keep enum contracts consistent.

Made-with: Cursor
2026-04-24 10:36:16 +02:00

1289 lines
51 KiB
Plaintext

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
extensions = [pgvector(map: "vector")]
}
generator client {
provider = "prisma-client-js"
previewFeatures = ["postgresqlExtensions"]
}
generator json {
provider = "prisma-json-types-generator"
}
enum PipelineTriggers {
responseCreated
responseUpdated
responseFinished
}
enum WebhookSource {
user
zapier
make
n8n
activepieces
}
/// Represents a webhook endpoint for receiving survey-related events.
/// Webhooks can be configured to receive notifications about response creation, updates, and completion.
///
/// @property id - Unique identifier for the webhook
/// @property name - Optional display name for the webhook
/// @property url - The endpoint URL where events will be sent
/// @property source - Origin of the webhook (user, zapier, make, etc.)
/// @property workspace - Associated workspace
/// @property triggers - Types of events that trigger this webhook
/// @property surveyIds - List of surveys this webhook is monitoring
model Webhook {
id String @id @default(cuid())
name String?
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @default(now()) @updatedAt @map(name: "updated_at")
url String
source WebhookSource @default(user)
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
workspaceId String
triggers PipelineTriggers[]
surveyIds String[]
secret String?
@@index([workspaceId])
}
/// Represents an attribute value associated with a contact.
/// Used to store custom properties and characteristics of contacts.
///
/// @property id - Unique identifier for the attribute
/// @property attributeKey - Reference to the attribute definition
/// @property contact - The contact this attribute belongs to
/// @property value - The string value of the attribute (used for string type + backwards compatibility)
/// @property valueNumber - Native numeric storage for number type attributes
/// @property valueDate - Native date storage for date type attributes
model ContactAttribute {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
attributeKey ContactAttributeKey @relation(fields: [attributeKeyId], references: [id], onDelete: Cascade)
attributeKeyId String
contact Contact @relation(fields: [contactId], references: [id], onDelete: Cascade)
contactId String
value String
valueNumber Float?
valueDate DateTime?
@@unique([contactId, attributeKeyId])
@@index([attributeKeyId, value])
@@index([attributeKeyId, valueNumber])
@@index([attributeKeyId, valueDate])
}
enum ContactAttributeType {
default
custom
}
enum ContactAttributeDataType {
string
number
date
}
/// Defines the possible attributes that can be assigned to contacts.
/// Acts as a schema for contact attributes within a workspace.
///
/// @property id - Unique identifier for the attribute key
/// @property isUnique - Whether the attribute must have unique values across contacts
/// @property key - The attribute identifier used in the system
/// @property name - Display name for the attribute
/// @property type - Whether this is a default or custom attribute
/// @property dataType - The data type of the attribute (string, number, date)
/// @property workspace - The workspace this attribute belongs to
model ContactAttributeKey {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
isUnique Boolean @default(false)
key String
name String?
description String?
type ContactAttributeType @default(custom)
dataType ContactAttributeDataType @default(string)
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
workspaceId String
attributes ContactAttribute[]
attributeFilters SurveyAttributeFilter[]
@@unique([key, workspaceId])
@@index([workspaceId, createdAt])
}
/// Represents a person or user who can receive and respond to surveys.
/// Contacts are workspace-specific and can have multiple attributes and responses.
///
/// @property id - Unique identifier for the contact
/// @property workspace - The workspace this contact belongs to
/// @property responses - Survey responses from this contact
/// @property attributes - Custom attributes associated with this contact
/// @property displays - Record of surveys shown to this contact
model Contact {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
workspaceId String
responses Response[]
attributes ContactAttribute[]
displays Display[]
@@index([workspaceId])
}
/// Stores a user's response to a survey, including their answers and metadata.
/// Each response is linked to a specific survey and optionally to a contact.
///
/// @property id - Unique identifier for the response
/// @property finished - Whether the survey was completed
/// @property survey - The associated survey
/// @property contact - The optional contact who provided the response
/// @property data - JSON object containing the actual response data
/// @property variables - Custom variables used in the response
/// @property ttc - Time to completion metrics
/// @property meta - Additional metadata about the response
model Response {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @default(now()) @updatedAt @map(name: "updated_at")
finished Boolean @default(false)
survey Survey @relation(fields: [surveyId], references: [id], onDelete: Cascade)
surveyId String
contact Contact? @relation(fields: [contactId], references: [id], onDelete: Cascade)
contactId String?
endingId String?
/// [ResponseData]
data Json @default("{}")
/// [ResponseVariables]
variables Json @default("{}")
/// [ResponseTtc]
ttc Json @default("{}")
/// [ResponseMeta]
meta Json @default("{}")
tags TagsOnResponses[]
quotaLinks ResponseQuotaLink[]
/// [ResponseContactAttributes]
contactAttributes Json?
// singleUseId, used to prevent multiple responses
singleUseId String?
language String?
displayId String? @unique
display Display? @relation(fields: [displayId], references: [id])
@@unique([surveyId, singleUseId])
@@index([createdAt])
@@index([surveyId, createdAt]) // to determine monthly response count
@@index([contactId, createdAt]) // to determine monthly identified users (persons)
}
/// Represents a label that can be applied to survey responses.
/// Used for categorizing and organizing responses within a workspace.
///
/// @property id - Unique identifier for the tag
/// @property name - Display name of the tag
/// @property responses - Survey responses tagged with this label
/// @property workspace - The workspace this tag belongs to
model Tag {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
name String
responses TagsOnResponses[]
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
workspaceId String
@@unique([workspaceId, name])
@@index([workspaceId])
}
/// Junction table linking tags to responses.
/// Enables many-to-many relationship between tags and responses.
///
/// @property response - The tagged response
/// @property tag - The tag applied to the response
model TagsOnResponses {
responseId String
response Response @relation(fields: [responseId], references: [id], onDelete: Cascade)
tagId String
tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade)
@@id([responseId, tagId])
}
enum SurveyStatus {
draft
inProgress
paused
completed
}
/// Records when a survey is shown to a user.
/// Tracks survey display history and response status.
///
/// @property id - Unique identifier for the display event
/// @property survey - The survey that was displayed
/// @property contact - The contact who saw the survey
/// @property response - The associated response if one exists
model Display {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
survey Survey @relation(fields: [surveyId], references: [id], onDelete: Cascade)
surveyId String
contact Contact? @relation(fields: [contactId], references: [id], onDelete: Cascade)
contactId String?
response Response?
@@index([surveyId])
@@index([contactId, createdAt])
}
/// Links surveys to specific trigger actions.
/// Defines which user actions should cause a survey to be displayed.
/// This is the connection table between Surveys and ActionClasses that determines
/// when and under what conditions a survey should be triggered.
///
/// @property id - Unique identifier for the trigger
/// @property survey - The survey to be triggered
/// @property actionClass - The action that triggers the survey
/// @property createdAt - When the trigger was created
/// @property updatedAt - When the trigger was last modified
model SurveyTrigger {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
survey Survey @relation(fields: [surveyId], references: [id], onDelete: Cascade)
surveyId String
actionClass ActionClass @relation(fields: [actionClassId], references: [id], onDelete: Cascade)
actionClassId String
@@unique([surveyId, actionClassId])
}
enum SurveyAttributeFilterCondition {
equals
notEquals
}
enum SurveyQuotaAction {
endSurvey
continueSurvey
}
enum ResponseQuotaLinkStatus {
screenedIn
screenedOut
}
/// Defines targeting rules for surveys based on contact attributes.
/// Used to show surveys only to contacts matching specific criteria.
///
/// @property id - Unique identifier for the filter
/// @property attributeKey - The contact attribute to filter on
/// @property survey - The survey being filtered
/// @property condition - The comparison operator to use
/// @property value - The value to compare against
model SurveyAttributeFilter {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
attributeKey ContactAttributeKey @relation(fields: [attributeKeyId], references: [id], onDelete: Cascade)
attributeKeyId String
survey Survey @relation(fields: [surveyId], references: [id], onDelete: Cascade)
surveyId String
condition SurveyAttributeFilterCondition
value String
@@unique([surveyId, attributeKeyId])
@@index([attributeKeyId])
}
enum SurveyType {
link
app
}
enum displayOptions {
displayOnce
displayMultiple
displaySome
respondMultiple
}
enum SurveyScriptMode {
add
replace
}
/// Represents a complete survey configuration including questions, styling, and display rules.
/// Core model for the survey functionality in Formbricks.
///
/// @property id - Unique identifier for the survey
/// @property name - Display name of the survey
/// @property type - Survey delivery method (link, web, website, app)
/// @property status - Current state of the survey (draft, active, completed, etc)
/// @property workspace - The workspace this survey belongs to
/// @property questions - JSON array containing survey questions configuration
/// @property displayOption - Rules for how often the survey can be shown
/// @property triggers - Actions that can trigger this survey
/// @property attributeFilters - Rules for targeting specific contacts
/// @property customHeadScripts - Survey-specific custom HTML scripts (self-hosted only)
/// @property customHeadScriptsMode - "add" (merge with workspace) or "replace" (override workspace)
model Survey {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
name String
redirectUrl String?
type SurveyType @default(app)
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
workspaceId String
creator User? @relation(fields: [createdBy], references: [id])
createdBy String?
status SurveyStatus @default(draft)
/// [SurveyWelcomeCard]
welcomeCard Json @default("{\"enabled\": false}")
/// [SurveyQuestions]
questions Json @default("[]")
/// [SurveyBlocks]
blocks Json[] @default([])
/// [SurveyEnding]
endings Json[] @default([])
/// [SurveyHiddenFields]
hiddenFields Json @default("{\"enabled\": false}")
/// [SurveyVariables]
variables Json @default("[]")
responses Response[]
quotas SurveyQuota[]
displayOption displayOptions @default(displayOnce)
recontactDays Int?
displayLimit Int?
triggers SurveyTrigger[]
/// [SurveyInlineTriggers]
inlineTriggers Json?
attributeFilters SurveyAttributeFilter[]
displays Display[]
autoClose Int?
autoComplete Int?
delay Int @default(0)
/// [SurveyClosedMessage]
surveyClosedMessage Json?
segmentId String?
segment Segment? @relation(fields: [segmentId], references: [id])
/// [SurveyWorkspaceOverwrites]
workspaceOverwrites Json?
/// [SurveyStyling]
styling Json?
/// [SurveySingleUse]
singleUse Json? @default("{\"enabled\": false, \"isEncrypted\": true}")
isVerifyEmailEnabled Boolean @default(false)
isSingleResponsePerEmailEnabled Boolean @default(false)
isBackButtonHidden Boolean @default(false)
isAutoProgressingEnabled Boolean @default(false)
isCaptureIpEnabled Boolean @default(false)
pin String?
displayPercentage Decimal?
languages SurveyLanguage[]
showLanguageSwitch Boolean?
followUps SurveyFollowUp[]
/// [SurveyRecaptcha]
recaptcha Json? @default("{\"enabled\": false, \"threshold\":0.1}")
/// [SurveyLinkMetadata]
metadata Json @default("{}")
connectorMappings ConnectorFormbricksMapping[]
slug String? @unique
customHeadScripts String?
customHeadScriptsMode SurveyScriptMode? @default(add)
@@unique([id, workspaceId])
@@index([segmentId])
@@index([workspaceId, updatedAt])
}
/// Represents a quota configuration for a survey.
/// Defines response limits and conditions for quota management.
///
/// @property id - Unique identifier for the quota
/// @property survey - The survey this quota belongs to
/// @property name - Display name for the quota
/// @property limit - Maximum number of responses allowed
/// @property conditions - JSON object containing quota conditions
/// @property action - Action to take when quota is reached
/// @property endingCardId - Optional ending card to show when quota is reached
model SurveyQuota {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
survey Survey @relation(fields: [surveyId], references: [id], onDelete: Cascade)
surveyId String
name String
limit Int
/// [SurveyQuotaLogic]
logic Json @default("{}")
action SurveyQuotaAction
endingCardId String?
quotaLinks ResponseQuotaLink[]
countPartialSubmissions Boolean @default(false)
@@unique([surveyId, name])
}
/// Junction table linking responses to quotas.
/// Tracks which responses counted towards specific quotas and their status.
///
/// @property response - The response that counted towards the quota
/// @property quota - The quota this response counted towards
/// @property status - Whether the response was screened in or out
model ResponseQuotaLink {
response Response @relation(fields: [responseId], references: [id], onDelete: Cascade)
responseId String
quota SurveyQuota @relation(fields: [quotaId], references: [id], onDelete: Cascade)
quotaId String
status ResponseQuotaLinkStatus
@@id([responseId, quotaId])
@@index([quotaId, status])
}
/// Defines follow-up actions for survey responses.
/// Enables automated actions based on specific survey response conditions.
///
/// @property id - Unique identifier for the follow-up
/// @property survey - The associated survey
/// @property name - Display name for the follow-up
/// @property trigger - Conditions that activate the follow-up
/// @property action - Actions to take when triggered
model SurveyFollowUp {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
survey Survey @relation(fields: [surveyId], references: [id], onDelete: Cascade)
surveyId String
name String
/// [SurveyFollowUpTrigger]
trigger Json
/// [SurveyFollowUpAction]
action Json
}
enum ActionType {
code
noCode
}
/// Represents a user action that can trigger surveys.
/// Used to define points in the user journey where surveys can be shown.
///
/// @property id - Unique identifier for the action
/// @property name - Display name of the action
/// @property type - Whether this is a code or no-code action
/// @property key - Unique identifier used in code implementation
/// @property noCodeConfig - Configuration for no-code setup
/// @property workspace - The workspace this action belongs to
model ActionClass {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
name String
description String?
type ActionType
key String?
/// [ActionClassNoCodeConfig]
noCodeConfig Json?
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
workspaceId String
surveyTriggers SurveyTrigger[]
@@unique([key, workspaceId])
@@unique([name, workspaceId])
@@index([workspaceId, createdAt])
}
enum IntegrationType {
googleSheets
notion
airtable
slack
}
/// Represents third-party service integrations.
/// Enables data flow between Formbricks and external services.
///
/// @property id - Unique identifier for the integration
/// @property type - The service being integrated (Google Sheets, Notion, etc.)
/// @property workspace - The workspace this integration belongs to
/// @property config - Service-specific configuration details
model Integration {
id String @id @default(cuid())
type IntegrationType
/// [IntegrationConfig]
config Json
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
workspaceId String
@@unique([type, workspaceId])
@@index([workspaceId])
}
enum DataMigrationStatus {
pending
applied
failed
}
/// Tracks database schema migrations.
/// Used to manage and track the state of data structure changes.
///
/// @property id - Unique identifier for the migration
/// @property name - Name of the migration
/// @property status - Current state of the migration (pending, applied, failed)
/// @property startedAt - When the migration began
/// @property finishedAt - When the migration completed
model DataMigration {
id String @id @default(cuid())
startedAt DateTime @default(now()) @map(name: "started_at")
finishedAt DateTime? @map(name: "finished_at")
name String @unique
status DataMigrationStatus
}
enum WidgetPlacement {
bottomLeft
bottomRight
topLeft
topRight
center
}
enum SurveyOverlay {
none
light
dark
}
/// Main grouping mechanism for resources in Formbricks.
/// Each organization can have multiple workspaces to separate different applications or products.
///
/// @property id - Unique identifier for the workspace
/// @property name - Display name of the workspace
/// @property organization - Reference to parent organization
/// @property surveys - Collection of surveys in this workspace
/// @property styling - Workspace-wide styling configuration
/// @property config - Workspace-specific configuration
/// @property recontactDays - Default recontact delay for surveys
/// @property placement - Default widget placement for in-app surveys
model Workspace {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
name String
legacyEnvironmentId String? @unique
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
organizationId String
/// [Styling]
styling Json @default("{\"allowStyleOverwrite\":true}")
/// [WorkspaceConfig]
config Json @default("{}")
recontactDays Int @default(7)
linkSurveyBranding Boolean @default(true) // Determines if the survey branding should be displayed in link surveys
inAppSurveyBranding Boolean @default(true) // Determines if the survey branding should be displayed in in-app surveys
placement WidgetPlacement @default(bottomRight)
clickOutsideClose Boolean @default(true)
overlay SurveyOverlay @default(none)
languages Language[]
/// [Logo]
logo Json?
workspaceTeams WorkspaceTeam[]
appSetupCompleted Boolean @default(false)
customHeadScripts String? // Custom HTML scripts for link surveys (self-hosted only)
feedbackRecordDirectoryWorkspaces FeedbackRecordDirectoryWorkspace[]
charts Chart[]
dashboards Dashboard[]
surveys Survey[]
contacts Contact[]
actionClasses ActionClass[]
contactAttributeKeys ContactAttributeKey[]
webhooks Webhook[]
tags Tag[]
segments Segment[]
integrations Integration[]
apiKeyWorkspaces ApiKeyWorkspace[]
connectors Connector[]
@@unique([organizationId, name])
}
/// Represents the top-level organizational hierarchy in Formbricks.
/// Self-hosting instances typically have a single organization, while Formbricks Cloud
/// supports multiple organizations with multi-tenancy.
///
/// @property id - Unique identifier for the organization
/// @property name - Display name of the organization
/// @property memberships - User memberships within the organization
/// @property workspaces - Collection of workspaces owned by the organization
/// @property billing - JSON field containing billing information
/// @property whitelabel - Whitelabel configuration for the organization
/// @property isAISmartToolsEnabled - Controls access to AI smart tools (e.g. translations) that never touch collected data
/// @property isAIDataAnalysisEnabled - Controls access to AI data analysis features that touch experience data
model Organization {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
name String
memberships Membership[]
workspaces Workspace[]
billing OrganizationBilling?
/// [OrganizationWhitelabel]
whitelabel Json @default("{}")
invites Invite[]
isAISmartToolsEnabled Boolean @default(false)
isAIDataAnalysisEnabled Boolean @default(false)
teams Team[]
apiKeys ApiKey[]
feedbackRecordDirectories FeedbackRecordDirectory[]
}
/// Stores billing and Stripe synchronization data for an organization.
///
/// @property organizationId - Reference to the organization
/// @property stripeCustomerId - Stripe customer ID for cloud billing
/// @property limits - Plan limits used by feature and quota checks
/// @property usageCycleAnchor - Stable anchor used to derive the current monthly usage cycle
/// @property stripe - Stripe synchronization snapshot data
model OrganizationBilling {
organizationId String @id @map(name: "organization_id")
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
stripeCustomerId String? @unique @map(name: "stripe_customer_id")
/// [OrganizationBillingPlanLimits]
limits Json
usageCycleAnchor DateTime? @map(name: "usage_cycle_anchor")
/// [OrganizationStripeBilling]
stripe Json?
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
}
enum OrganizationRole {
owner
manager
member
billing
}
/// Links users to organizations with specific roles.
/// Manages organization membership and permissions.
/// Core model for managing user access within organizations.
///
/// @property organization - The organization the user belongs to
/// @property user - The member user
/// @property accepted - Whether the user has accepted the membership
/// @property role - User's role within the organization (owner, manager, member, billing)
model Membership {
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
organizationId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String
accepted Boolean @default(false)
role OrganizationRole @default(member)
@@id([userId, organizationId])
@@index([organizationId])
}
/// Represents pending invitations to join an organization.
/// Used to manage the process of adding new users to an organization.
/// Once accepted, invites are converted into memberships.
///
/// @property id - Unique identifier for the invite
/// @property email - Email address of the invited user
/// @property name - Optional display name for the invited user
/// @property organization - The organization sending the invite
/// @property creator - The user who created the invite
/// @property acceptor - The user who accepted the invite (if accepted)
/// @property expiresAt - When the invite becomes invalid
/// @property role - Intended role for the invited user
/// @property teamIds - Teams the user will be added to upon acceptance
model Invite {
id String @id @default(uuid())
email String
name String?
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
organizationId String
creator User @relation("inviteCreatedBy", fields: [creatorId], references: [id])
creatorId String
acceptor User? @relation("inviteAcceptedBy", fields: [acceptorId], references: [id], onDelete: Cascade)
acceptorId String?
createdAt DateTime @default(now())
expiresAt DateTime
role OrganizationRole @default(member)
teamIds String[] @default([])
@@index([email, organizationId])
@@index([organizationId])
}
/// Represents enhanced API authentication keys with organization-level ownership.
/// Used for authenticating API requests to Formbricks with more granular permissions.
///
/// @property id - Unique identifier for the API key
/// @property label - Optional descriptive name for the key
/// @property hashedKey - Securely stored API key
/// @property organization - The organization this key belongs to
/// @property createdBy - User ID who created this key
/// @property lastUsedAt - Timestamp of last usage
/// @property apiKeyWorkspaces - Workspaces this key has access to
model ApiKey {
id String @id @default(cuid())
createdAt DateTime @default(now())
createdBy String?
lastUsedAt DateTime?
label String
hashedKey String
lookupHash String? @unique
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
apiKeyWorkspaces ApiKeyWorkspace[]
/// [OrganizationAccess]
organizationAccess Json @default("{}")
@@index([organizationId])
}
/// Defines permission levels for API keys.
/// Controls what operations an API key can perform.
enum ApiKeyPermission {
read
write
manage
}
/// Links API keys to workspaces with specific permissions.
/// Enables granular access control for API keys across workspaces.
///
/// @property id - Unique identifier for the workspace access entry
/// @property apiKey - The associated API key
/// @property workspace - The workspace being accessed
/// @property permission - Level of access granted
model ApiKeyWorkspace {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
apiKeyId String
apiKey ApiKey @relation(fields: [apiKeyId], references: [id], onDelete: Cascade)
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
workspaceId String
permission ApiKeyPermission
@@unique([apiKeyId, workspaceId])
@@index([workspaceId])
}
enum IdentityProvider {
email
github
google
azuread
openid
saml
}
/// Stores third-party authentication account information.
/// Enables OAuth and other external authentication methods.
///
/// @property id - Unique identifier for the account
/// @property user - The Formbricks user who owns this account
/// @property provider - The authentication provider (GitHub, Google, etc.)
/// @property providerAccountId - User ID from the provider
/// @property access_token - OAuth access token
/// @property refresh_token - OAuth refresh token
model Account {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String
type String
provider String
providerAccountId String
access_token String? @db.Text
refresh_token String? @db.Text
expires_at Int?
ext_expires_in Int?
token_type String?
scope String?
id_token String? @db.Text
session_state String?
@@unique([provider, providerAccountId])
@@index([userId])
}
/// Stores active authentication sessions for revocable server-side login state.
///
/// @property sessionToken - Opaque token stored in the browser cookie
/// @property user - The Formbricks user who owns this session
/// @property expires - Hard expiry for the session
model Session {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
sessionToken String @unique
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String
expires DateTime
@@index([userId])
}
/// Stores one-time verification tokens used by Auth.js adapter flows.
model VerificationToken {
identifier String
token String
expires DateTime
@@unique([identifier, token])
}
/// Stores the active password reset token for a user.
/// Tokens are opaque, hashed at rest, revocable, and single-use.
model PasswordResetToken {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
tokenHash String @unique @map(name: "token_hash")
expiresAt DateTime @map(name: "expires_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String @unique
@@index([expiresAt])
}
/// Represents a user in the Formbricks system.
/// Central model for user authentication and profile management.
///
/// @property id - Unique identifier for the user
/// @property name - Display name of the user
/// @property email - User's email address
/// @property twoFactorEnabled - Whether 2FA is active
/// @property memberships - Organizations the user belongs to
/// @property notificationSettings - User's notification preferences
model User {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
name String
email String @unique
emailVerified DateTime? @map(name: "email_verified")
twoFactorSecret String?
twoFactorEnabled Boolean @default(false)
backupCodes String?
password String?
identityProvider IdentityProvider @default(email)
identityProviderAccountId String?
memberships Membership[]
accounts Account[]
sessions Session[]
passwordResetToken PasswordResetToken?
groupId String?
invitesCreated Invite[] @relation("inviteCreatedBy")
invitesAccepted Invite[] @relation("inviteAcceptedBy")
/// [UserNotificationSettings]
notificationSettings Json @default("{}")
/// [Locale]
locale String @default("en-US")
surveys Survey[]
charts Chart[] @relation("chartCreatedBy")
dashboards Dashboard[] @relation("dashboardCreatedBy")
connectors Connector[]
teamUsers TeamUser[]
lastLoginAt DateTime?
isActive Boolean @default(true)
}
/// Defines a segment of contacts based on attributes.
/// Used for targeting surveys to specific user groups.
///
/// @property id - Unique identifier for the segment
/// @property title - Display name of the segment
/// @property filters - Rules defining the segment
/// @property isPrivate - Whether the segment is private
/// @property workspace - The workspace this segment belongs to
model Segment {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
title String
description String?
isPrivate Boolean @default(true)
/// [SegmentFilter]
filters Json @default("[]")
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
workspaceId String
surveys Survey[]
@@unique([workspaceId, title])
@@index([workspaceId])
}
/// Represents a supported language in the system.
/// Used for multilingual survey support.
///
/// @property id - Unique identifier for the language
/// @property code - Language code (e.g., 'en-US')
/// @property alias - Optional friendly name
/// @property workspace - The workspace this language is enabled for
model Language {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
code String
alias String?
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
workspaceId String
surveyLanguages SurveyLanguage[]
@@unique([workspaceId, code])
}
/// Links surveys to their supported languages.
/// Manages which languages are available for each survey.
///
/// @property language - The supported language
/// @property survey - The associated survey
/// @property default - Whether this is the default language
/// @property enabled - Whether this language is currently active
model SurveyLanguage {
language Language @relation(fields: [languageId], references: [id], onDelete: Cascade)
languageId String
surveyId String
survey Survey @relation(fields: [surveyId], references: [id], onDelete: Cascade)
default Boolean @default(false)
enabled Boolean @default(true)
@@id([languageId, surveyId])
@@index([surveyId])
}
/// Represents a team within an organization.
/// Enables group-based access control and collaboration.
///
/// @property id - Unique identifier for the team
/// @property name - Display name of the team
/// @property organization - The parent organization
/// @property teamUsers - Users who are part of this team
/// @property workspaceTeams - Workspaces this team has access to
model Team {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
name String
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
teamUsers TeamUser[]
workspaceTeams WorkspaceTeam[]
@@unique([organizationId, name])
}
enum TeamUserRole {
admin
contributor
}
/// Links users to teams with specific roles.
/// Manages team membership and permissions.
///
/// @property team - The associated team
/// @property user - The team member
/// @property role - User's role within the team
model TeamUser {
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
teamId String
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
role TeamUserRole
@@id([teamId, userId])
@@index([userId])
}
enum WorkspaceTeamPermission {
read
readWrite
manage
}
/// Defines team access to specific workspaces.
/// Manages workspace-level permissions for teams.
///
/// @property workspace - The accessed workspace
/// @property team - The team receiving access
/// @property permission - Level of access granted
model WorkspaceTeam {
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
workspaceId String
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
teamId String
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
permission WorkspaceTeamPermission @default(read)
@@id([workspaceId, teamId])
@@index([teamId])
}
enum ChartType {
area
bar
line
pie
big_number
}
/// Represents a chart/visualization that can be used in multiple dashboards.
/// Charts are reusable components that query analytics data.
///
/// @property id - Unique identifier for the chart
/// @property name - Display name of the chart
/// @property type - Type of visualization (bar, line, pie, etc.)
/// @property workspace - The workspace this chart belongs to
/// @property query - Cube.js query configuration (JSON)
/// @property config - Chart-specific configuration (colors, labels, etc.)
/// @property createdBy - User who created the chart
/// @property dashboards - Dashboards that use this chart
model Chart {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
name String
type ChartType
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
workspaceId String
/// [ChartQuery] - Cube.js query configuration
query Json @default("{}")
/// [ChartConfig] - Visualization configuration (colors, labels, formatting)
config Json @default("{}")
creator User? @relation("chartCreatedBy", fields: [createdBy], references: [id], onDelete: SetNull)
createdBy String?
feedbackRecordDirectory FeedbackRecordDirectory @relation(fields: [feedbackRecordDirectoryId], references: [id])
feedbackRecordDirectoryId String
widgets DashboardWidget[]
@@unique([workspaceId, name])
@@index([workspaceId, createdAt])
@@index([feedbackRecordDirectoryId])
}
/// Represents a dashboard containing multiple charts.
/// Dashboards aggregate analytics insights at the workspace level.
///
/// @property id - Unique identifier for the dashboard
/// @property name - Display name of the dashboard
/// @property workspace - The workspace this dashboard belongs to
/// @property widgets - Charts on this dashboard
/// @property createdBy - User who created the dashboard
model Dashboard {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
name String
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
workspaceId String
creator User? @relation("dashboardCreatedBy", fields: [createdBy], references: [id], onDelete: SetNull)
createdBy String?
widgets DashboardWidget[]
@@unique([workspaceId, name])
@@index([workspaceId, createdAt])
}
/// Represents a chart widget on a dashboard.
/// Widgets are positioned using a grid layout system.
///
/// @property id - Unique identifier for the widget
/// @property dashboard - The dashboard this widget belongs to
/// @property chart - The chart displayed in this widget (chart name is used for display)
/// @property layout - Grid layout configuration (x, y, width, height)
/// @property order - Display order within the dashboard
model DashboardWidget {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
dashboard Dashboard @relation(fields: [dashboardId], references: [id], onDelete: Cascade)
dashboardId String
chart Chart @relation(fields: [chartId], references: [id], onDelete: Cascade)
chartId String
/// [WidgetLayout] - Grid layout: { x, y, w, h }
layout Json @default("{\"x\":0,\"y\":0,\"w\":4,\"h\":3}")
order Int @default(0)
@@index([dashboardId, order])
}
enum ConnectorType {
formbricks_survey
csv
}
enum ConnectorStatus {
active
paused
error
}
enum HubFieldType {
text
categorical
nps
csat
ces
rating
number
boolean
date
}
/// Base connector for all integration types.
/// Connects external data sources to the Hub for feedback record creation.
///
/// @property id - Unique identifier for the connector
/// @property name - Display name for the connector
/// @property type - Type of connector (formbricks_survey, webhook, csv, email, slack)
/// @property status - Current state of the connector (active, paused)
/// @property environment - The environment this connector belongs to
/// @property config - Type-specific configuration (e.g., webhook secret, S3 config)
/// @property formbricksMappings - Element mappings for Formbricks connectors
/// @property fieldMappings - Field mappings for other connector types
model Connector {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
name String
type ConnectorType
status ConnectorStatus @default(active)
workspaceId String
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
feedbackRecordDirectoryId String
feedbackRecordDirectory FeedbackRecordDirectory @relation(fields: [feedbackRecordDirectoryId], references: [id], onDelete: Cascade)
formbricksMappings ConnectorFormbricksMapping[]
fieldMappings ConnectorFieldMapping[]
lastSyncAt DateTime? @map(name: "last_sync_at")
createdBy String? @map(name: "created_by")
creator User? @relation(fields: [createdBy], references: [id], onDelete: SetNull)
@@unique([id, workspaceId])
@@unique([workspaceId, name])
@@index([type])
@@index([feedbackRecordDirectoryId])
}
/// Maps survey elements to Hub FeedbackRecords for Formbricks connectors.
/// Each row represents one element that will create FeedbackRecords when answered.
///
/// @property id - Unique identifier for the mapping
/// @property connector - The parent connector
/// @property survey - The survey containing the element
/// @property elementId - The element ID within the survey (from blocks[].elements[].id)
/// @property hubFieldType - The field_type to use in Hub (text, nps, rating, etc.)
/// @property customFieldLabel - Optional override for the element headline as field_label in Hub
model ConnectorFormbricksMapping {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
connectorId String
workspaceId String
connector Connector @relation(fields: [connectorId, workspaceId], references: [id, workspaceId], onDelete: Cascade)
surveyId String
survey Survey @relation(fields: [surveyId, workspaceId], references: [id, workspaceId], onDelete: Cascade)
elementId String
hubFieldType HubFieldType
customFieldLabel String? @map(name: "custom_field_label")
@@unique([workspaceId, connectorId, surveyId, elementId])
@@index([workspaceId, surveyId])
@@index([surveyId])
}
/// Generic field mapping for Webhook, CSV, Email, Slack connectors.
/// Maps source fields to Hub FeedbackRecord fields.
///
/// @property id - Unique identifier for the mapping
/// @property connector - The parent connector
/// @property sourceFieldId - Field path for webhook (e.g., "user.id"), column name for CSV
/// @property targetFieldId - Hub field (collected_at, field_id, value_text, etc.)
/// @property staticValue - If set, use this value instead of reading from sourceFieldId
model ConnectorFieldMapping {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
connectorId String
workspaceId String
connector Connector @relation(fields: [connectorId, workspaceId], references: [id, workspaceId], onDelete: Cascade)
sourceFieldId String @map(name: "source_field_id")
targetFieldId String @map(name: "target_field_id")
staticValue String? @map(name: "static_value")
@@unique([workspaceId, connectorId, sourceFieldId, targetFieldId])
}
/// Represents a feedback record directory (Hub tenant) owned by an organization.
/// Directories group feedback data and are assigned to workspaces for access control.
///
/// @property id - Unique identifier for the directory
/// @property name - Display name of the directory
/// @property isArchived - Soft delete flag
/// @property organization - The parent organization
/// @property workspaces - Workspaces assigned to this directory
model FeedbackRecordDirectory {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
name String
isArchived Boolean @default(false)
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
workspaces FeedbackRecordDirectoryWorkspace[]
connectors Connector[]
charts Chart[]
@@unique([organizationId, name])
}
/// Links feedback record directories to workspaces (workspaces).
/// Manages which workspaces can access a given directory.
///
/// @property feedbackRecordDirectory - The directory being accessed
/// @property workspace - The workspace receiving access
model FeedbackRecordDirectoryWorkspace {
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
feedbackRecordDirectoryId String
feedbackRecordDirectory FeedbackRecordDirectory @relation(fields: [feedbackRecordDirectoryId], references: [id], onDelete: Cascade)
workspaceId String
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
@@id([feedbackRecordDirectoryId, workspaceId])
@@index([workspaceId])
}