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 clients and/or linear_teams in Supabase (or run the backfill script, which reads from Supabase).
  • Future agents: Query clients and linear_teams (and linear_team_type_seed for 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 clients and optionally to a HubSpot company.
  • Internal teams are represented in Supabase via the linear_teams table (no clients row).
  • ID matches: linear_team_id, Supabase clients.id (for clients), and hubspot_company_id are stored and kept in sync so features (ticket generation, deal analysis, reporting) can join across systems.

Where IDs Live

SystemEntityID field(s)Notes
LinearTeamid (UUID), keyFrom Linear API list_teams.
SupabaseClientid (UUID), linear_team_idOne row per delivery client.
SupabaseLinear team rowlinear_team_id (unique), client_id (FK), hubspot_company_idTable linear_teams: all Linear teams.
HubSpotCompanyCompany ID (numeric string)From HubSpot API / Polytomic.

Tables to query

TableUse
clientsOne 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_teamsOne 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_seedOptional 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 migration 007_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):

ColumnTypeDescription
idUUIDPK.
linear_team_idTEXTLinear team UUID (unique).
linear_team_keyTEXTe.g. LMN, PLT, GTM.
nameTEXTDisplay name.
typeTEXTclient | internal.
client_idUUIDFK to clients.id when type = 'client'.
hubspot_company_idTEXTWhen known (client or internal).
created_at / updated_atTIMESTAMPTZ
  • Client teams: Ensure a clients row exists with the same linear_team_id; set linear_teams.client_id to that clients.id, and sync hubspot_company_id to both clients and linear_teams when known.
  • Internal teams: Row in linear_teams only; client_id null; hubspot_company_id optional.

linear_team_type_seed (migration 009)

  • identifier — Team key or name (matches Linear).
  • typeclient | 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 writes

Requires: LINEAR_API_KEY or BRAINFORGE_PLATFORM_LINEAR_APP_TOKEN, NEXT_PUBLIC_SUPABASE_URL (or BF_SUPABASE_URL), BF_SUPABASE_SERVICE_KEY.

The script:

  1. Loads client vs internal from linear_team_type_seed (rows with type = 'client' match on Linear team key or name).
  2. Treats any team with a matching clients.linear_team_id as client even if missing from the seed (common onboarding path).
  3. Fetches all Linear teams via paginated GraphQL requests.
  4. Upserts linear_teams with client_id and hubspot_company_id from clients when linked.
  5. Preserves existing linear_teams.hubspot_company_id when 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

  1. From Linear (source of team list)
    Run the backfill script periodically, or call Linear and upsert linear_teams by linear_team_id.

  2. Client teams
    For each client team: ensure clients has linear_team_id; set linear_teams.client_id; sync hubspot_company_id on clients and/or linear_teams when known.

  3. HubSpot companies
    Store HubSpot company ID in clients.hubspot_company_id and/or linear_teams.hubspot_company_id. See playbook hubspot-api-setup.md for API access.

  4. New client onboarding
    Create clients with linear_team_id; run backfill or update linear_teams with type = 'client' and client_id; add hubspot_company_id when 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

  1. Resolve client from Linear team id: Query clients where linear_team_id = <id>, or query linear_teams where linear_team_id = <id> then take client_id and query clients by id.
  2. Resolve client from HubSpot company id: Query clients where hubspot_company_id = <id>.
  3. List client teams / mappings: Query linear_teams where type = ‘client’, join to clients on client_id.
  4. Add/update mapping: Insert or update clients; run backfill to refresh linear_teams, or update linear_teams directly.
  5. Classify new Linear teams: Prefer linear_team_type_seed and clients.linear_team_id; backfill encodes the combined rules above.

Code references

  • Lookup by Linear team: apps/platform/src/lib/linearTicketGeneration.tsgetClientNameFromTeamId, getClientEmbeddingTablesFromTeamId (query clients by linear_team_id).
  • Clients API: apps/platform/src/app/api/brainforge/clients/ — GET/PATCH; hubspot_company_id supported where implemented.
  • Client form: ClientFormModal and ClientsContextlinear_team_id and hubspot_company_id where 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 clients row exists with correct linear_team_id (client teams only).
  • linear_teams row exists with linear_team_id, type, and (if client) client_id pointing to clients.id.
  • hubspot_company_id set on clients and/or linear_teams when the HubSpot company is known.