crm

Contacts, companies, and contact-company relationships for customer management

CRM

Contacts, companies, and the many-to-many relationship between them. Contacts represent external people you do business with (not application users). Companies represent the organizations those contacts belong to.

Tables

contacts

ColumnTypeDescription
iduuidPrimary key
org_iduuidFK to organizations
owner_iduuidFK to users. The user responsible for this contact
first_nametextContact first name
last_nametextContact last name
emailtextEmail address
phonetextPhone number
typecontact_typeLifecycle stage: lead, prospect, customer, partner, or other
titletextJob title
sourcetextHow this contact was acquired (e.g. website, referral, trade show)
avatar_urltextURL to avatar image
address_line1textStreet address line 1
address_line2textStreet address line 2
citytextCity
statetextState or province
postal_codetextPostal or zip code
countrytextCountry
created_attimestamptzRow creation timestamp
updated_attimestamptzLast update timestamp (auto-set by trigger)
metadatajsonbArbitrary JSON

companies

ColumnTypeDescription
iduuidPrimary key
org_iduuidFK to organizations
owner_iduuidFK to users. The user responsible for this company
nametextCompany name
domaintextCompany web domain
industrytextIndustry vertical
sizetextCompany size bracket (e.g. 1-10, 11-50, 51-200)
websitetextFull website URL
phonetextPhone number
address_line1textStreet address line 1
address_line2textStreet address line 2
citytextCity
statetextState or province
postal_codetextPostal or zip code
countrytextCountry
annual_revenuenumericEstimated annual revenue in cents or smallest currency unit
created_attimestamptzRow creation timestamp
updated_attimestamptzLast update timestamp (auto-set by trigger)
metadatajsonbArbitrary JSON

contact_companies

ColumnTypeDescription
iduuidPrimary key
org_iduuidFK to organizations
contact_iduuidFK to contacts
company_iduuidFK to companies
titletextContact's role or title at this company
is_primarybooleanWhether this is the contact's current primary company
started_atdateWhen the contact started at this company
ended_atdateWhen the contact left this company
created_attimestamptzRow creation timestamp
updated_attimestamptzLast update timestamp (auto-set by trigger)
metadatajsonbArbitrary JSON

Unique constraint on (contact_id, company_id).

Enums

contact_type

ValueDescription
leadInitial contact, not yet qualified
prospectQualified and actively being pursued
customerConverted to a paying customer
partnerBusiness partner or collaborator
otherDoes not fit the other categories

Row-Level Security

All tables have RLS enabled and are scoped to the current user's organization via get_user_org_id().

  • contacts: SELECT/INSERT/UPDATE for own org. DELETE requires admin.
  • companies: SELECT/INSERT/UPDATE for own org. DELETE requires admin.
  • contact_companies: SELECT/INSERT/UPDATE for own org. DELETE requires admin.

Dependencies

  • identity -- organizations, users, and RLS helper functions

Example Queries

Get all contacts at a specific company:

SELECT c.first_name, c.last_name, c.email, cc.title
FROM contacts c
JOIN contact_companies cc ON cc.contact_id = c.id
WHERE cc.company_id = '<company_id>'
  AND cc.is_primary = true
ORDER BY c.last_name;

Search contacts by type with company info:

SELECT
  c.first_name || ' ' || coalesce(c.last_name, '') AS name,
  c.email,
  c.type,
  co.name AS company
FROM contacts c
LEFT JOIN contact_companies cc ON cc.contact_id = c.id AND cc.is_primary = true
LEFT JOIN companies co ON co.id = cc.company_id
WHERE c.type = 'lead'
ORDER BY c.created_at DESC;

List all companies with their primary contact count:

SELECT
  co.name,
  co.industry,
  co.size,
  count(cc.id) AS contact_count
FROM companies co
LEFT JOIN contact_companies cc ON cc.company_id = co.id AND cc.is_primary = true
WHERE co.org_id = get_user_org_id()
GROUP BY co.id, co.name, co.industry, co.size
ORDER BY contact_count DESC;

Find contacts without a company association:

SELECT c.first_name, c.last_name, c.email, c.type, c.source
FROM contacts c
LEFT JOIN contact_companies cc ON cc.contact_id = c.id
WHERE c.org_id = get_user_org_id()
  AND cc.id IS NULL
ORDER BY c.created_at DESC;

Get a contact's full company history:

SELECT
  co.name AS company,
  cc.title,
  cc.started_at,
  cc.ended_at,
  cc.is_primary
FROM contact_companies cc
JOIN companies co ON co.id = cc.company_id
WHERE cc.contact_id = '<contact_id>'
ORDER BY cc.started_at DESC NULLS FIRST;