---
title: Opportunities (CRM Deals)
feature: true
featureGroup: core
featureState: beta
description: Sales opportunities with pipeline stages, amounts and contact linking — part of the CRM pack
date: 2026-05-06
---

The **Opportunity** type represents a sales deal — a potential revenue object that moves through a pipeline. Without opportunities a CRM is just a contact database; with them it tracks the actual sales process.

Opportunities ship as part of the **CRM pack** (`crm-pack`) alongside Contacts and Emails. They can be installed standalone via the **Opportunities** module if you only need the deal pipeline without the rest of the CRM.

## When to use

- Tracking sales deals through stages from lead to closed-won/closed-lost
- Linking revenue forecasts to specific contacts and companies
- Reporting on pipeline value, win rates and expected close dates

## Schema

The schema lives in `packages/shared/src/typesystem/types/opportunity.ts` and is registered as the schema extension `opportunities:v1` in `packages/shared/src/typesystem/schema-extensions.ts`.

| Property            | Type   | Required | Description                                                                           |
| ------------------- | ------ | -------- | ------------------------------------------------------------------------------------- |
| `name`              | string | yes      | Short, human-readable name of the deal                                                |
| `amount`            | number | no       | Expected revenue amount                                                               |
| `currency`          | string | no       | ISO 4217 currency code: `EUR` (default), `USD`, `GBP`, `CHF`                          |
| `stage`             | enum   | yes      | One of `lead`, `qualified`, `proposal`, `negotiation`, `won`, `lost` (default `lead`) |
| `probability`       | number | no       | Probability of closing the deal, expressed as a percentage (0–100)                    |
| `expectedCloseDate` | number | no       | Expected close date as a Unix timestamp in milliseconds                               |
| `companyId`         | string | no       | Reference to the Company entity this opportunity belongs to (foreign-id widget)       |
| `primaryContactId`  | string | no       | Reference to the primary Contact entity for this opportunity (foreign-id widget)      |
| `notes`             | string | no       | Free-form notes                                                                       |

`name` and `stage` are the only required fields. The default stage is `lead` and the default currency is `EUR`.

## Pipeline stages

The default pipeline ships with six stages:

| Value         | German       | English     |
| ------------- | ------------ | ----------- |
| `lead`        | Lead         | Lead        |
| `qualified`   | Qualifiziert | Qualified   |
| `proposal`    | Angebot      | Proposal    |
| `negotiation` | Verhandlung  | Negotiation |
| `won`         | Gewonnen     | Won         |
| `lost`        | Verloren     | Lost        |

Stages are stored as a configurable enum on the type. Once the type is created in a project the project owner can customise the list via the regular type-editor flow.

## Relations

When the Opportunity type is installed alongside a Contact type from the same install batch, the install process wires them up automatically (see `packages/shared/src/templates/extensions/opportunity.type.ts`):

The `links` relation property is enabled, so emails, notes, calendar events and tasks can be linked to an Opportunity through the standard relations system — not only to a Contact.

## Backoffice page

Installing the **Opportunities BOM** extension adds a navigation entry at `/opportunities` with a data view showing the columns:

`name`, `companyId`, `stage`, `amount`, `probability`, `expectedCloseDate`

The page uses the standard widget framework and inherits the same filtering, sorting and bulk-action behaviour as other backoffice modules.

## Kanban pipeline board

The data view widget supports a **kanban** display mode in addition to `table` and `grid`. It turns any data type into a board-like experience (think GitHub Projects): one column per value of a grouping field, with draggable cards in between.

Configure it on the widget instance:

- **`mode: "kanban"`** — start the widget in board mode (users can still switch modes via the display-mode menu).
- **`kanbanGroupBy`** — the field whose values become columns, e.g. `stage` for Opportunities or `status` for Tasks. Required for kanban mode; when omitted the widget falls back to the agenda `groupBy` field.

Behaviour:

- **Columns** are derived from the grouping field's schema (its `select` values or `enum`), so column order and labels follow the type definition. Configured columns always render, even when empty. Values found in the data but missing from the schema are appended; rows without a value land in a trailing "Unassigned" column.
- **Cards** show the type's `representationText` in a distinct header strip, with the same property body as the grid view below it. The body is capped at 300px and scrolls vertically when it overflows, so cards stay readable without dominating the column. Columns fill the available board height and scroll when they hold more cards than fit.
- **Drag and drop** a card to another column to change the grouping field. The mutation is written optimistically through SignalDB's local-first sync, so the move appears instantly and persists in the background.

Cards and columns expose `data-testid` attributes (`kanban-card-<id>`, `kanban-column-<value>`) for E2E testing.

## Installation

The Opportunity module is available in the store as a standalone module and is pre-selected in the **CRM pack**:

```ts
// packages/shared/src/templates/store.collections.ts
modules: [FeatureName.COMPANY, FeatureName.CONTACT, FeatureName.OPPORTUNITY, FeatureName.EMAIL]
```

| Identifier                                | Purpose                                       |
| ----------------------------------------- | --------------------------------------------- |
| `FeatureName.OPPORTUNITY`                 | Opportunities module                          |
| `FeatureExtensionName.OPPORTUNITIES_TYPE` | Creates the Opportunity data type             |
| `FeatureExtensionName.OPPORTUNITIES_BOM`  | Adds the `/opportunities` navigation page     |
| `FeatureRef.OPPORTUNITIES_TYPE`           | Reference for the created type entity         |
| `FeatureRef.OPPORTUNITIES_BOM`            | Reference for the created BOM compound widget |
| `FeatureRef.OPPORTUNITIES_BOM_NAV_ENTRY`  | Reference for the created navigation entry    |

Existing CRM projects can install the new module from the store at any time — no migration is required because the type and BOM are created on demand by the store install flow rather than baked into existing tenant data.

## Duplicate detection

Duplicate detection is enabled with a 0.85 threshold against the `name` field. Two deals named "Q4 Renewal" are flagged as likely duplicates at import or creation time.
