Linear ↔ Supabase ↔ HubSpot ID Mapping
Purpose: Define how Linear teams align with Supabase client/team records and HubSpot companies so IDs match across systems. The mapping between Linear teams, Supabase clients, and HubSpot companies is stored only in Supabase — query and update Supabase for current state; do not treat markdown as source of truth for live mappings.
Last updated: 2026-03-17
Source of truth: Supabase
- To know which Linear team maps to which client or HubSpot company: Query Supabase.
- To add or change a mapping: Update
clientsand/orlinear_teamsin Supabase (or run the backfill script, which reads from Supabase). - Future agents: Query
clientsandlinear_teams(andlinear_team_type_seedfor client vs internal classification). This doc describes schema and workflows, not live ID pairs.
Goal
- Linear teams (client + internal) have a corresponding record in Supabase where applicable.
- Client teams map to Supabase
clientsand optionally to a HubSpot company. - Internal teams are represented in Supabase via the
linear_teamstable (noclientsrow). - ID matches:
linear_team_id, Supabaseclients.id(for clients), andhubspot_company_idare stored and kept in sync so features (ticket generation, deal analysis, reporting) can join across systems.
Where IDs Live
| System | Entity | ID field(s) | Notes |
|---|---|---|---|
| Linear | Team | id (UUID), key | From Linear API list_teams. |
| Supabase | Client | id (UUID), linear_team_id | One row per delivery client. |
| Supabase | Linear team row | linear_team_id (unique), client_id (FK), hubspot_company_id | Table linear_teams: all Linear teams. |
| HubSpot | Company | Company ID (numeric string) | From HubSpot API / Polytomic. |
Tables to query
| Table | Use |
|---|---|
clients | One row per delivery client. linear_team_id = Linear team UUID; hubspot_company_id = HubSpot company ID. Query to resolve client by Linear team or by HubSpot company. |
linear_teams | One row per Linear team. linear_team_id, name, linear_team_key, type (client | internal), client_id (FK to clients), hubspot_company_id. Query to list all teams, see which are client, or resolve client from Linear team id. |
linear_team_type_seed | Optional seed for “is this team client or internal?” by team key or name. Backfill uses this; teams linked in clients.linear_team_id are also classified as client. Once linear_teams is populated, linear_teams.type is the persisted classification. |
Supabase Schema
clients (existing)
id— Supabase UUID (primary key).linear_team_id— Linear team UUID; links this client to a Linear team.hubspot_company_id— HubSpot company ID (added in migration007_linear_teams_and_clients_hubspot_company_id.sql).
So for each client team: one clients row with linear_team_id and optional hubspot_company_id.
linear_teams (migration 007)
One row per Linear team (client + internal):
| Column | Type | Description |
|---|---|---|
id | UUID | PK. |
linear_team_id | TEXT | Linear team UUID (unique). |
linear_team_key | TEXT | e.g. LMN, PLT, GTM. |
name | TEXT | Display name. |
type | TEXT | client | internal. |
client_id | UUID | FK to clients.id when type = 'client'. |
hubspot_company_id | TEXT | When known (client or internal). |
created_at / updated_at | TIMESTAMPTZ |
- Client teams: Ensure a
clientsrow exists with the samelinear_team_id; setlinear_teams.client_idto thatclients.id, and synchubspot_company_idto bothclientsandlinear_teamswhen known. - Internal teams: Row in
linear_teamsonly;client_idnull;hubspot_company_idoptional.
linear_team_type_seed (migration 009)
identifier— Team key or name (matches Linear).type—client|internal.
Applying migrations: Use Supabase MCP (apply_migration) or Supabase CLI; prefer automation over ad-hoc Dashboard SQL when possible. See .cursor/rules/supabase-migrations-mcp.mdc if present.
Backfill script
From the platform repo (apps/platform):
npx tsx scripts/backfill-linear-teams.ts # fetch Linear teams, upsert linear_teams
npx tsx scripts/backfill-linear-teams.ts --dry-run # print only, no DB writesRequires: LINEAR_API_KEY or BRAINFORGE_PLATFORM_LINEAR_APP_TOKEN, NEXT_PUBLIC_SUPABASE_URL (or BF_SUPABASE_URL), BF_SUPABASE_SERVICE_KEY.
The script:
- Loads client vs internal from
linear_team_type_seed(rows withtype = 'client'match on Linear team key or name). - Treats any team with a matching
clients.linear_team_idas client even if missing from the seed (common onboarding path). - Fetches all Linear teams via paginated GraphQL requests.
- Upserts
linear_teamswithclient_idandhubspot_company_idfromclientswhen linked. - Preserves existing
linear_teams.hubspot_company_idwhen the client row has no HubSpot ID (avoids wiping manually assigned IDs on repeat runs).
Do not read live classification from markdown lists; use the seed table and clients as above.
How to Keep in Sync
-
From Linear (source of team list)
Run the backfill script periodically, or call Linear and upsertlinear_teamsbylinear_team_id. -
Client teams
For each client team: ensureclientshaslinear_team_id; setlinear_teams.client_id; synchubspot_company_idonclientsand/orlinear_teamswhen known. -
HubSpot companies
Store HubSpot company ID inclients.hubspot_company_idand/orlinear_teams.hubspot_company_id. See playbookhubspot-api-setup.mdfor API access. -
New client onboarding
Createclientswithlinear_team_id; run backfill or updatelinear_teamswithtype = 'client'andclient_id; addhubspot_company_idwhen known.
One Linear team per client
Target: one client = one Linear team. If Linear has two teams for the same client, consolidate in Linear first (move issues, archive one team). Then ensure a single clients row with that team’s linear_team_id and the correct hubspot_company_id.
How agents should use this
- Resolve client from Linear team id: Query
clientswherelinear_team_id= <id>, or querylinear_teamswherelinear_team_id= <id> then takeclient_idand queryclientsby id. - Resolve client from HubSpot company id: Query
clientswherehubspot_company_id= <id>. - List client teams / mappings: Query
linear_teamswheretype= ‘client’, join toclientsonclient_id. - Add/update mapping: Insert or update
clients; run backfill to refreshlinear_teams, or updatelinear_teamsdirectly. - Classify new Linear teams: Prefer
linear_team_type_seedandclients.linear_team_id; backfill encodes the combined rules above.
Code references
- Lookup by Linear team:
apps/platform/src/lib/linearTicketGeneration.ts—getClientNameFromTeamId,getClientEmbeddingTablesFromTeamId(queryclientsbylinear_team_id). - Clients API:
apps/platform/src/app/api/brainforge/clients/— GET/PATCH;hubspot_company_idsupported where implemented. - Client form:
ClientFormModalandClientsContext—linear_team_idandhubspot_company_idwhere implemented. - Migrations:
007_linear_teams_and_clients_hubspot_company_id.sql,009_linear_team_type_seed.sql(and related index migrations).
Checklist: ID alignment per client
- Linear team exists and is classified (client vs internal) in seed and/or
clients. - Supabase
clientsrow exists with correctlinear_team_id(client teams only). -
linear_teamsrow exists withlinear_team_id,type, and (if client)client_idpointing toclients.id. -
hubspot_company_idset onclientsand/orlinear_teamswhen the HubSpot company is known.