<!-- @generated by packages/sdk/scripts/copy-reference-docs.mjs -- DO NOT EDIT -->
<!-- Regenerate: pnpm scaffold:sync -->

﻿---
title: Build and Extend CRM
description: Map the CRM platform primitives available to SDK projects: shared UI pages, sidebar composition, data hooks, action definitions, workflow adapters, System Interfaces, and org-model extension boundaries.
---

# Build and Extend CRM

Use this recipe when a downstream project wants to build on the shared CRM instead of forking it.

Good trigger phrases:

- "Add a CRM reports page."
- "Build a custom deal workspace."
- "Read and update deals from a workflow."
- "Add a Send Quote button."
- "Change the CRM pipeline stages for this business."

CRM is a layered platform surface, not one component:

- **Organization OS:** System access, sales pipeline semantics, quick access, and labels live in the organization model.
- **Shared UI:** CRM pages, sidebars, workbench panels, overview widgets, and Kanban/detail components live in `@elevasis/ui`.
- **Headless hooks:** deal, company, contact, note, task, list, transition, and action hooks live under `@elevasis/ui/hooks`.
- **Action system:** `ActionDef`, `deriveActions`, and the caller-supplied provider-level `crmActions` catalog configure deal actions. The published `@elevasis/sdk` surface ships no default Elevasis action catalog -- projects supply their own `ActionDef[]`.
- **Workflow adapters:** `crm` and `acqDb` from `@elevasis/sdk/worker` let workflows read and mutate CRM/acquisition data through platform tools.
- **System API Interface:** `sales.crm` uses the flat `system.apiInterface` marker for its API-backed CRM boundary. Readiness is derived from scoped resources, ontology bindings, required catalogs, and scoped topology grants. Cross-system lead-gen handoff belongs in scoped topology, not a top-level Bridge Contract or a hand-authored handoff interface field. Generated contract docs expose the current CRM shapes in `node_modules/@elevasis/sdk/reference/scaffold/reference/contracts.md`.

## Decision Table

| User wants                                                                       | Start here                                                                                          | Notes                                                                                                                                                             |
| -------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Change CRM System availability, labels, pipeline stages, resource descriptors, or API readiness | `core/config/organization-model.ts`                                                                 | Treat this as Organization OS work. Mark `sales.crm.apiInterface` active only when scoped CRM resources and ontology are ready for API-backed actions.             |
| Add CRM sidebar nav or a CRM route                                               | `@elevasis/ui/features/crm` and `node_modules/@elevasis/sdk/reference/scaffold/ui/customization.md` | Prefer manifest/sidebar composition. Do not fork shared source first.                                                                                             |
| Wrap a shared CRM page with project chrome                                       | `DealsListPage`, `DealDetailPage`, `CrmOverview` from `@elevasis/ui/features/crm`                   | Keep route files thin and put project-specific logic in local feature modules.                                                                                    |
| Build a custom deal page                                                         | `useDealDetail`, `useDealNotes`, `useDealTasks`, `useExecuteAction` from `@elevasis/ui/hooks`       | Use hooks for platform data and compose your own UI.                                                                                                              |
| Add, hide, or replace deal action buttons                                        | [customize-crm-actions.md](customize-crm-actions.md)                                                | Start with the shared `crmActions` provider path; use project-owned UI when a custom workflow path is outside platform-known/default action dispatch constraints. |
| Read or mutate CRM data inside a workflow                                        | `crm` or `acqDb` from `@elevasis/sdk/worker`                                                        | `organizationId` is injected server-side by the platform dispatcher. Do not pass it from workflow code.                                                           |
| Add a new persisted CRM column or table                                          | Platform/API migration work, not just scaffold work                                                 | Update DB, core schemas/types, API service/handlers, hooks, docs, and scaffold contracts together.                                                                |

## Published CRM Surfaces

| Surface                                                                                              | Import from                 | Use for                                              |
| ---------------------------------------------------------------------------------------------------- | --------------------------- | ---------------------------------------------------- |
| `crmManifest`, `CRM_ITEMS`, `CrmSidebar`, `CrmSidebarTop`, `CrmSidebarMiddle`                        | `@elevasis/ui/features/crm` | Feature registration and sidebar composition         |
| `CrmOverview`, `DealsListPage`, `DealDetailPage`, `CompanyDetailPage`                                | `@elevasis/ui/features/crm` | Shared CRM pages you can route to or wrap            |
| `MyTasksPanel`, `SavedViewsPanel`, `QuickCreateActions`                                              | `@elevasis/ui/features/crm` | Workbench/sidebar panels for custom CRM sidebars     |
| `PipelineFunnelWidget`, `ActivityFeedWidget`, `MetricsStrip`                                         | `@elevasis/ui/features/crm` | Overview widgets for custom dashboards               |
| `KanbanBoard`, `DealKanbanCard`, `DealDrawer`                                                        | `@elevasis/ui/components`   | Lower-level CRM deal UI primitives                   |
| `useDeals`, `useDealDetail`, `useTransitionItem`, `useExecuteAction`, `useDealNotes`, `useDealTasks` | `@elevasis/ui/hooks`        | Headless deal and task data access                   |
| `useCompanies`, `useContacts`, `useLists`, `useBatchTelemetry`                                       | `@elevasis/ui/hooks`        | Acquisition substrate data access                    |
| `ElevasisUIProvider`, `ElevasisCoreProvider`, `useElevasisServices`                                  | `@elevasis/ui/provider`     | Provider setup, API access, and organization context |
| `ActionDef`, `deriveActions`                                                                         | `@elevasis/sdk`             | Deal action configuration and render-time derivation |
| `crm`, `acqDb`                                                                                       | `@elevasis/sdk/worker`      | Workflow-side CRM/acquisition platform adapters      |

Read the generated contracts before changing typed boundaries:

`node_modules/@elevasis/sdk/reference/scaffold/reference/contracts.md`

Look for the **CRM Platform Primitives** section. It includes deal stages, deal rows, task shapes, API schemas, action definitions, and the focused CRM workflow adapter map. The broader acquisition/list adapter maps live under **Lead Gen Platform Primitives**.

Deal list and detail responses include a server-derived `priority` object. Use `deal.priority.bucketKey`, `rank`, `label`, `reason`, `latestActivityAt`, and `nextActionAt` when composing custom deal workspaces or workflow-side follow-up logic instead of re-deriving priority from `updated_at`.

## 1. Extend CRM Navigation or Sidebar

For a simple nav addition, extend `CRM_ITEMS` and override the CRM system manifest:

```tsx
// ui/src/routes/__root.tsx
import { crmManifest, CRM_ITEMS, CrmSidebar, CrmSidebarMiddle } from '@elevasis/ui/features/crm'
import type { SystemModule } from '@elevasis/ui/provider'
import type { NavItem } from '@elevasis/ui/layout'
import { IconFileText } from '@tabler/icons-react'

const customCrmItems: NavItem[] = [
  ...CRM_ITEMS,
  { label: 'Reports', to: '/crm/reports', icon: IconFileText, exact: false }
]

const CustomCrmSidebar = () => (
  <CrmSidebar>
    <CrmSidebarMiddle items={customCrmItems} />
  </CrmSidebar>
)

export const customCrmManifest: SystemModule = {
  ...crmManifest,
  sidebar: CustomCrmSidebar
}
```

Then replace `crmManifest` with `customCrmManifest` in the local `SYSTEM_MANIFESTS` array and add the matching route under `ui/src/routes/crm/`.

For structural changes, compose `CrmSidebarTop`, `SubshellNavList`, `SubshellSidebarSection`, `MyTasksPanel`, and `QuickCreateActions`. The full sidebar decision tree lives in `node_modules/@elevasis/sdk/reference/scaffold/ui/customization.md`.

## 2. Wrap Shared CRM Pages

If the shared page is close, keep it and wrap it:

```tsx
// ui/src/routes/crm/deals.index.tsx
import { createFileRoute } from '@tanstack/react-router'
import { DealsListPage } from '@elevasis/ui/features/crm'
import { Stack } from '@mantine/core'
import { ProjectAnnouncementBanner } from '@/lib/components/ProjectAnnouncementBanner'

export const Route = createFileRoute('/crm/deals/')({
  component: DealsRoute
})

function DealsRoute() {
  return (
    <Stack gap={0}>
      <ProjectAnnouncementBanner context="crm-deals" />
      <DealsListPage />
    </Stack>
  )
}
```

Use the same pattern for `CrmOverview`, `DealDetailPage`, and `CompanyDetailPage`.

## 3. Build a Custom Deal Page

When the project needs custom layout or additional workflows, use the hooks directly:

```tsx
import { Button, Group, Stack, Textarea } from '@mantine/core'
import { useState } from 'react'
import { useDealDetail, useDealNotes, useCreateDealNote, useExecuteAction } from '@elevasis/ui/hooks'

export function CustomDealWorkspace({ dealId }: { dealId: string }) {
  const { data: deal, isLoading } = useDealDetail(dealId)
  const { data: notes = [] } = useDealNotes(dealId)
  const createNote = useCreateDealNote()
  const executeAction = useExecuteAction({ dealId })
  const [body, setBody] = useState('')

  if (isLoading) return null
  if (!deal) return <div>Deal not found</div>

  return (
    <Stack>
      <h1>{deal.contact_email}</h1>
      <Group>
        <Button onClick={() => executeAction.mutate({ key: 'move_to_proposal' })}>
          Move to Proposal
        </Button>
      </Group>
      <Textarea value={body} onChange={(event) => setBody(event.currentTarget.value)} />
      <Button onClick={() => createNote.mutate({ dealId, body })}>Add Note</Button>
      <pre>{JSON.stringify(notes, null, 2)}</pre>
    </Stack>
  )
}
```

`useExecuteAction` is for platform-known/default action keys. Start custom action UI with the shared `crmActions` provider path; when a project-owned workflow is outside that server-dispatched action set, call `/execute` or `/execute-async` through `useElevasisServices`, or follow [customize-crm-actions.md](customize-crm-actions.md).

## 4. Read and Mutate CRM Data in Workflows

Inside deployed workflows, use worker adapters instead of browser hooks or direct database access:

```ts
// operations/src/sales/follow-up-stale-deals.ts
import type { WorkflowDefinition } from '@elevasis/sdk'
import { crm } from '@elevasis/sdk/worker'
import { resourceDescriptors } from '@core/config/organization-model'
import { z } from 'zod'

const inputSchema = z.object({
  stage: z.string().default('proposal')
})

const outputSchema = z.object({
  touched: z.number()
})

export const followUpStaleDealsWorkflow: WorkflowDefinition = {
  config: {
    resource: resourceDescriptors.followUpStaleDeals,
    resourceId: resourceDescriptors.followUpStaleDeals.id,
    name: 'Follow Up Stale Deals',
    type: resourceDescriptors.followUpStaleDeals.kind,
    version: '1.0.0',
    status: 'dev',
  },
  contract: { inputSchema, outputSchema },
  steps: {
    followUp: {
      id: 'followUp',
      name: 'Follow Up',
      inputSchema,
      outputSchema,
      next: null,
      handler: async (input) => {
        const deals = await crm.listDeals({ stage: input.stage })

        for (const deal of deals) {
          if (!['follow_up_due', 'stale'].includes(deal.priority.bucketKey)) continue

          await crm.createDealTask({
            dealId: deal.id,
            title: 'Follow up',
            kind: 'email'
          })
          await crm.recordActivity({
            dealId: deal.id,
            type: 'workflow.follow_up_task_created',
            title: 'Follow-up task created',
            payload: { workflow: 'follow-up-stale-deals' }
          })
        }

        return { touched: deals.length }
      }
    }
  },
  entryPoint: 'followUp'
}
```

Use `crm` for focused CRM deal reads, notes, tasks, activity, and cleanup. Use `acqDb` when the workflow needs the broader acquisition substrate: companies, contacts, lists, list-stage transitions, enrichment data, social posts, or lower-level deal sync methods.

## 5. Customize Pipeline Semantics

CRM System identity is `sales.crm`, but the organization-model domain is `sales`.

For stage labels or stage sets, update the project model:

`core/config/organization-model.ts`

Keep these boundaries straight:

- Org-model sales pipelines describe business semantics.
- `DealStage` and platform action defaults describe the current shared platform deal state vocabulary.
- UI wrappers can relabel and route around shared surfaces, but changing persisted platform stages requires coordinated core/API/UI updates.
- Do not add `sales.actions` to the org model in v1. Deal action customization is covered by [customize-crm-actions.md](customize-crm-actions.md).
- Mark `sales.crm.apiInterface` active only after CRM object, pipeline, action, and resource refs are ready.
- Model behavior that intentionally crosses from lead-gen into CRM through lead-gen resources that bind the CRM pipeline plus a scoped topology grant to `sales.crm.apiInterface`.

## Verify

Run the checks for the surfaces you touched:

```bash
pnpm -C ui run check
pnpm -C operations run check
pnpm -C operations exec elevasis-sdk check
```

If you changed platform-level CRM contracts in the monorepo, the platform maintainer must also regenerate and verify scaffold output:

```bash
pnpm scaffold:sync
pnpm scaffold:verify
```
