Skip to main content

Database Schema

GTM Clarity uses PostgreSQL (Neon serverless) with Drizzle ORM. The schema consists of 17 tables across 15 schema files, all scoped by tenant_id for multi-tenant isolation.

Entity Relationship Diagram

Base Column Helpers

All tables use shared column helpers from src/server/db/schema/base.ts:

tenantColumns

Added to every table for multi-tenant isolation:

ColumnTypeNullableDescription
tenant_idtextNoForeign key to tenants.id

timestampColumns

Added to most tables for tracking:

ColumnTypeDefaultDescription
created_attimestamptznow()Row creation time
updated_attimestamptznow()Last modification time

softDeleteColumns

Added to entities that support soft delete:

ColumnTypeDefaultDescription
deleted_attimestamptznullDeletion timestamp (null = active)

Table Reference

tenants

Root table for multi-tenant isolation. Maps 1:1 with Clerk organizations.

ColumnTypeConstraintsDescription
idtextPK, UUIDInternal tenant ID
clerk_org_idtextNOT NULL, UNIQUEClerk organization ID
nametextNOT NULLOrganization display name
slugtextNOT NULLURL-safe identifier
settingsjsonbDEFAULT {}Tenant configuration overrides
created_attimestamptzNOT NULL, DEFAULT now()Creation timestamp
updated_attimestamptzNOT NULL, DEFAULT now()Last update timestamp

accounts

CRM accounts with firmographic data used for fit scoring.

ColumnTypeConstraintsDescription
idtextPK, UUIDAccount ID
tenant_idtextNOT NULLTenant scope
nametextNOT NULLCompany name
domaintextWebsite domain
industrytextIndustry classification
employee_countintegerNumber of employees
annual_revenuenumericAnnual revenue
sourcetextData source identifier
external_idtextID in source system
source_datajsonbRaw source record
created_attimestamptzNOT NULLCreation time
updated_attimestamptzNOT NULLLast update time
deleted_attimestamptzSoft delete marker

Indexes: tenant_id, (tenant_id, external_id) UNIQUE, domain


people

Contacts and leads linked to accounts.

ColumnTypeConstraintsDescription
idtextPK, UUIDPerson ID
tenant_idtextNOT NULLTenant scope
account_idtextFK accounts.idParent account
emailtextEmail address
first_nametextFirst name
last_nametextLast name
titletextJob title
phonetextPhone number
sourcetextData source
external_idtextID in source system
source_datajsonbRaw source record
created_attimestamptzNOT NULLCreation time
updated_attimestamptzNOT NULLLast update time
deleted_attimestamptzSoft delete marker

Indexes: tenant_id, account_id, email, (tenant_id, external_id) UNIQUE


opportunities

Sales opportunities linked to accounts.

ColumnTypeConstraintsDescription
idtextPK, UUIDOpportunity ID
tenant_idtextNOT NULLTenant scope
account_idtextFK accounts.id, NOT NULLParent account
nametextNOT NULLOpportunity name
stagetextDeal stage
amountnumericDeal value
close_datetimestamptzExpected close date
sourcetextData source
external_idtextID in source system
source_datajsonbRaw source record
created_attimestamptzNOT NULLCreation time
updated_attimestamptzNOT NULLLast update time
deleted_attimestamptzSoft delete marker

Indexes: tenant_id, account_id, (tenant_id, external_id) UNIQUE, stage


activities

Multi-channel engagement events used for engagement scoring.

ColumnTypeConstraintsDescription
idtextPK, UUIDActivity ID
tenant_idtextNOT NULLTenant scope
person_idtextFK people.idPerson who performed the activity
account_idtextFK accounts.idAssociated account
typetextNOT NULLActivity type (e.g., email_open, page_view)
channeltextChannel (e.g., email, web, events)
occurred_attimestamptzNOT NULLWhen the activity happened
propertiesjsonbAdditional event properties
created_attimestamptzNOT NULLRecord creation time
updated_attimestamptzNOT NULLLast update time

Indexes: tenant_id, person_id, account_id, (tenant_id, occurred_at), type, channel


engagements

Computed scores for entities (people, accounts, opportunities).

ColumnTypeConstraintsDescription
idtextPK, UUIDEngagement record ID
tenant_idtextNOT NULLTenant scope
entity_typetextNOT NULLperson, account, or opportunity
entity_idtextNOT NULLID of the scored entity
fit_scorenumericFit score (0-100)
engagement_scorenumericEngagement score (0-100)
combined_scorenumericWeighted combined score
tiertextAssigned tier (hot, warm, cool, cold)
scored_attimestamptzNOT NULL, DEFAULT now()When the score was computed
score_versionintegerDEFAULT 1Scoring config version
score_breakdownjsonbDetailed scoring components
created_attimestamptzNOT NULLRecord creation time
updated_attimestamptzNOT NULLLast update time

Indexes: (tenant_id, entity_type, entity_id) UNIQUE, (tenant_id, tier), scored_at


connectors

Connector instances with encrypted credentials.

ColumnTypeConstraintsDescription
idtextPK, UUIDConnector ID
tenant_idtextNOT NULLTenant scope
typetextNOT NULLsalesforce, customerio, delivrai, o365, fathom
nametextNOT NULLDisplay name
configjsonbEncrypted configuration
statustextDEFAULT disconnectedConnection status
last_sync_attimestamptzLast successful sync
last_sync_cursortextPagination cursor
last_sync_errortextLast error message
field_mappingsjsonbLegacy field mappings
created_attimestamptzNOT NULLCreation time
updated_attimestamptzNOT NULLLast update time

Indexes: tenant_id, (tenant_id, type) UNIQUE


scoring_configs

Per-tenant scoring configuration with versioning.

ColumnTypeConstraintsDescription
idtextPK, UUIDConfig ID
tenant_idtextNOT NULLTenant scope
typetextNOT NULLfit, engagement, or combined
configjsonbNOT NULLFull configuration object
versionintegerDEFAULT 1Version number
is_activebooleanDEFAULT trueActive flag
created_attimestamptzNOT NULLCreation time
updated_attimestamptzNOT NULLLast update time

Indexes: (tenant_id, type) UNIQUE, tenant_id


field_mappings

Source-to-canonical field mapping configuration.

ColumnTypeConstraintsDescription
idtextPK, UUIDMapping ID
tenant_idtextNOT NULLTenant scope
connector_idtextFK connectors.id, NOT NULLParent connector
entity_typetextNOT NULLEntity being mapped
mappingsjsonbNOT NULLArray of FieldMappingEntry
is_defaultbooleanDEFAULT falseSystem default flag
created_attimestamptzNOT NULLCreation time
updated_attimestamptzNOT NULLLast update time

Indexes: (tenant_id, connector_id, entity_type) UNIQUE, tenant_id


writeback_configs

Score writeback configuration per connector.

ColumnTypeConstraintsDescription
idtextPK, UUIDConfig ID
tenant_idtextNOT NULLTenant scope
connector_idtextFK connectors.id, NOT NULLTarget connector
entity_typetextNOT NULLEntity type for writeback
field_mapjsonbNOT NULLArray of WritebackFieldMap
enabledbooleanDEFAULT falseWriteback enabled flag
last_writeback_attimestamptzLast successful writeback
last_writeback_errortextLast error
created_attimestamptzNOT NULLCreation time
updated_attimestamptzNOT NULLLast update time

Indexes: (tenant_id, connector_id, entity_type) UNIQUE, tenant_id


sync_logs

Sync operation history and audit trail.

ColumnTypeConstraintsDescription
idtextPK, UUIDLog entry ID
tenant_idtextNOT NULLTenant scope
connector_idtextFK connectors.id, NOT NULLConnector that synced
sync_typetextNOT NULLfull or incremental
statustextNOT NULL, DEFAULT runningrunning, completed, failed
started_attimestamptzNOT NULL, DEFAULT now()Start time
completed_attimestamptzCompletion time
records_createdintegerDEFAULT 0New records
records_updatedintegerDEFAULT 0Updated records
records_deletedintegerDEFAULT 0Deleted records
records_failedintegerDEFAULT 0Failed records
error_messagetextError text
error_detailsjsonbStructured error details
cursortextSync cursor at completion

Indexes: (tenant_id, connector_id), (connector_id, started_at)


audit_log

Append-only compliance audit trail.

ColumnTypeConstraintsDescription
idtextPK, UUIDEntry ID
tenant_idtextNOT NULLTenant scope
user_idtextNOT NULLActor's Clerk user ID
actiontextNOT NULLAction performed
resourcetextNOT NULLResource type
resource_idtextAffected resource ID
changesjsonbBefore/after diff
metadatajsonbAdditional context
created_attimestamptzNOT NULL, DEFAULT now()Action timestamp

Indexes: (tenant_id, created_at), (tenant_id, resource), user_id


buying_groups

Buying groups per account, optionally scoped by solution.

ColumnTypeConstraintsDescription
idtextPK, UUIDGroup ID
tenant_idtextNOT NULLTenant scope
account_idtextFK accounts.id, NOT NULLParent account
opportunity_idtextFK opportunities.idLinked opportunity
solution_tagtextSolution scope (null = account-level)
nametextGroup name
completeness_scorenumericRole completeness (0-100)
role_template_idtextApplied role template
created_attimestamptzNOT NULLCreation time
updated_attimestamptzNOT NULLLast update time

Indexes: tenant_id, account_id, opportunity_id, (tenant_id, account_id, solution_tag) UNIQUE


buying_group_members

Junction table linking people to buying groups with role assignment.

ColumnTypeConstraintsDescription
idtextPK, UUIDMember ID
tenant_idtextNOT NULLTenant scope
buying_group_idtextFK buying_groups.id, NOT NULLParent group
person_idtextFK people.idPerson in the group
roletextDetected or confirmed role
role_confidencenumericDetection confidence (0-1)
role_sourcetextHow role was determined
is_confirmedbooleanDEFAULT falseManually confirmed
is_discoveredbooleanDEFAULT falseFound via contact discovery
discovery_sourcetextSource of discovery
discovered_emailtextEmail found during discovery
created_attimestamptzNOT NULLCreation time
updated_attimestamptzNOT NULLLast update time

Indexes: tenant_id, buying_group_id, person_id, (buying_group_id, person_id) UNIQUE


role_templates

Configurable role templates for buying group completeness scoring.

ColumnTypeConstraintsDescription
idtextPK, UUIDTemplate ID
tenant_idtextNOT NULLTenant scope
nametextNOT NULLTemplate name
solution_tagtextSolution scope
rolesjsonbNOT NULLArray of { role, required, weight }
is_defaultbooleanDEFAULT falseDefault template flag
created_attimestamptzNOT NULLCreation time
updated_attimestamptzNOT NULLLast update time

Indexes: tenant_id


fathom_meetings

Fathom.video meeting records stored separately from activities (transcripts are too large for the activities table).

ColumnTypeConstraintsDescription
idtextPK, UUIDMeeting record ID
tenant_idtextNOT NULLTenant scope
fathom_meeting_idtextNOT NULLFathom's meeting ID
activity_idtextFK activities.idLinked activity record
titletextMeeting title
started_attimestamptzMeeting start time
ended_attimestamptzMeeting end time
participants_jsonjsonbArray of { name, email, role }
transcript_jsonjsonbArray of { speaker, text, startTime, endTime }
summary_texttextMeeting summary
sentiment_scoretextSentiment analysis (future)
created_attimestamptzNOT NULLCreation time
updated_attimestamptzNOT NULLLast update time

Indexes: tenant_id, (fathom_meeting_id, tenant_id) UNIQUE


o365_config

O365 connector privacy and DPA configuration.

ColumnTypeConstraintsDescription
idtextPK, UUIDConfig ID
tenant_idtextNOT NULLTenant scope
connector_idtextFK connectors.idParent connector
dpa_accepted_attimestamptzDPA acceptance time (null = blocked)
dpa_accepted_by_user_idtextUser who accepted
allowed_domainsjsonbDEFAULT []Domain allowlist for extraction
tenant_o365_domaintextTenant's own domain (excluded)
created_attimestamptzNOT NULLCreation time
updated_attimestamptzNOT NULLLast update time

Indexes: tenant_id, connector_id UNIQUE

Drizzle ORM Usage

GTM Clarity uses Drizzle ORM for type-safe database access:

# Run pending migrations
npm run drizzle migrate

# Open interactive DB browser
npm run drizzle studio
tip

Use Drizzle Studio (npm run drizzle studio) during development to visually inspect and query your database at https://local.drizzle.studio.