Companies — Technical Reference
This page covers the data models, architecture, and security rules behind the B2B Companies module.
Data Models
companies
Stores organization-level records.
| Column | Type | Description |
|---|---|---|
id | uuid | Primary key |
workspace_id | uuid | Tenant isolation |
name | text | Required. The organization's name |
domain | text | Optional website domain (e.g., acme.com) |
phone | text | Optional main phone number |
address_line1 | text | Optional street address line 1 |
address_line2 | text | Optional street address line 2 |
city | text | Optional city |
state | text | Optional state or province |
postal_code | text | Optional ZIP / postal code |
country | text | Optional country |
notes | text | Optional free-form notes |
created_by | uuid | FK → user_profiles.id (creator) |
updated_by | uuid | FK → user_profiles.id (last updater) |
created_at | timestamptz | Record creation timestamp |
updated_at | timestamptz | Record update timestamp |
company_contacts
Junction table mapping contacts to companies in a many-to-many relationship.
| Column | Type | Description |
|---|---|---|
company_id | uuid | Primary Key, FK → companies.id (ON DELETE CASCADE) |
contact_id | uuid | Primary Key, FK → contacts.id (ON DELETE CASCADE) |
job_title | text | Contact's role at the company |
work_email | text | Business email |
work_phone | text | Business phone |
is_primary | boolean | Flag designating the primary contact for the organization |
Design Note: A unique constraint on
(company_id, contact_id)prevents duplicate associations. Both foreign keys useON DELETE CASCADEto automatically clean up relationship mappings when either a company or a contact is deleted.
Architecture
Module Gating & UI Tabs
The Companies module is an optional feature. Access is controlled via workspace-level feature toggles:
- The
/companiessidebar link is filtered by the workspace module toggle status. - The Companies tab on the Contact detail view is conditionally rendered based on the
companiesmodule toggle. - The Deals tab on the Company detail view is conditionally rendered based on both the
companiesanddealsmodule toggles.
Case-Insensitive Search & Debounce
The /companies directory table features a search bar that queries the backend:
- Filters by
name,domain, orphoneusing a case-insensitive partial match (ILIKE). - Debounced by 300ms on the client side to minimize redundant API and database server requests during user input.
Live Contact Selection Picker
When linking a contact to a company:
- The dropdown list triggers a database search only after a minimum of 2 characters is typed.
- Already linked contact IDs are passed to the query to filter them out of the search results, preventing duplicate associations.
Phone Number Formatting
Main organization phone numbers and coworker work phone numbers are normalized for display as (XXX) XXX-XXXX using the same cosmetic formatting utility applied to contact phone numbers. Raw database values remain unformatted.
Company Detail Query (getCompanyById)
To populate the Company Detail view, the getCompanyById server action retrieves the company record and performs database joins to resolve:
created_by_profile: Resolves the user profile name (first_name,last_name) of the team member who created the company.updated_by_profile: Resolves the user profile name (first_name,last_name) of the team member who last modified the company.
These profiles are used to render the standard unified vertical audit trails in the UI.
Performance & Hydration Optimization
Server-Side Pre-fetching & Lazy Loading
- Server Component (
pages/companies/page.tsx): Pre-fetches the initialgetCompanieslist server-side. - Client Component (
companies-client.tsx): Manages search, client filters, and list sorting. - Dynamic imports: The
AddCompanyDialogis lazily and conditionally imported on demand, reducing the bundle size. - State gates: Uses
isFirstQueryRefto prevent duplicate fetches on initial load andinitialWorkspaceIdRefsync hooks to reset filters only on workspace changes.
Security
Row-Level Security (RLS)
The companies and company_contacts tables enforce workspace-level isolation using RLS. Deletion operations are additionally restricted in code to users with admin or owner roles.
| Table | Operation | Allowed Roles / Policies |
|---|---|---|
companies | SELECT | All workspace members |
companies | INSERT | All workspace members |
companies | UPDATE | All workspace members |
companies | DELETE | Workspace members with Admin or Owner roles (requireRole) |
company_contacts | SELECT | All workspace members |
company_contacts | INSERT | All workspace members |
company_contacts | UPDATE | All workspace members |
company_contacts | DELETE | All workspace members |
Note: Although any member can link or unlink a contact (modifying
company_contacts), only users with roleAdminorOwnercan delete acompaniesrecord entirely.