Hype StackHypeStack

CRM

Companies, contacts, and deals on a pipeline, with notes, tasks, files, and a timeline on every record. This is the pack-crm pack. It is a starting point you extend in code, not a hosted CRM with a settings screen for everything.

It installs into the admin app only. The CRM is your own back office: the people who run the product use it, and the users of your product never see it. There is one CRM for the whole product, shared by every admin.

What you need

  • Postgres and Valkey, both already in the base template. Valkey counts API requests per key; when it is down, the API keeps answering and the limit is off.
  • Object storage (RustFS locally) for file attachments. The pack creates its own bucket, crm-files, on first upload.
  • No third-party accounts and no environment variables.

What it depends on

  • A SaaS starter (Better Auth or WorkOS). The pack needs auth and admin: it uses the starter's admin app, its admin roles, and its admin list.
  • Any layout that shells the admin app. Records open in a docked side panel, which every layout provides as PanelShell.

What you get

Lists (all in the admin app, under CRM in the sidebar)

  • /companies, /contacts, /deals, and /tasks: tables where you click a cell to change it. An "Add new" row at the bottom creates a record from its name.
  • Filter, sort, pick which fields are columns, drag a header to reorder them, drag a column edge to resize it, and group by owner, stage, or company. All of it lives in the URL, so a link shows exactly what you see.
  • Hover a cell for a copy button and an edit button, so a domain or a phone number can be grabbed without opening an editor.
  • Saved views, private or shared with every admin.
  • A bulk bar for selected rows: set the owner, add a tag, merge two duplicates, delete, restore.
  • Trash with restore. Deleted records are removed for good after 30 days.
  • CSV import for companies and contacts with column mapping, and CSV export of any list as filtered.

Deals

  • Several pipelines, each with stages you rename, reorder, colour, and mark as open, won, or lost.
  • A board per pipeline with totals per stage. Dragging a card onto a lost stage asks for the reason.
  • A calendar by expected close date. Tasks have one by due date. Drag to reschedule.
  • /sales: open and weighted pipeline value, this month's wins and losses, deals closing next, your tasks.

Records

  • A record opens beside its list in a panel with no backdrop. Resize it, keep editing the table behind it, click another row to swap the record. The same content is also a full page at /companies/$companyId and so on.
  • Fields grouped into sections and edited in place, linked records, and tabs for the timeline, tasks, notes, and files.
  • The timeline is written in the same database transaction as each change.
  • Cmd+K searches companies, contacts, and deals from anywhere in the app. Star a record or a view to keep it one click away.

On a phone

  • There are no mobile app screens. The admin screens are responsive: tables scroll sideways, the board scrolls by stage, and a record opens full width with a back button where there is no room to dock a panel.

For integrations

  • /crm-api/v1: the same companies, contacts, deals, tasks, and notes behind an API key (Authorization: Bearer crm_live_...), limited to 120 requests a minute per key.
  • Webhooks for any timeline event, signed with HMAC SHA-256 in the x-crm-signature header (t=<unix seconds>,v1=<hex of "<t>.<body>">), retried with backoff, with a delivery log and a resend button.

Both are managed under CRM > Settings.

Who can get in

Only admins. Every route under /admin/crm runs behind the starter's admin guard: no session is a 401, and a signed-in user who is not an admin is a 403. The admin app itself sits behind the same check.

WhoCan do
Any adminEverything on records: companies, contacts, deals, tasks, notes, files, their own views, trash
Super adminAlso pipelines and stages, renaming and deleting tags, shared views, API keys, webhooks, and deleting for good
Signed-in userNothing
API key/crm-api/v1 only. A key is created by a super admin, shown once, and stored as a hash

/crm-api/v1 is the one way in without an admin session, and it exists only if a super admin creates a key. If you do not want it, delete routes/crm-api and its line in routes/index.ts.

routes/crm/crm.access.integration.test.ts reads the router and fails when someone adds a route outside these guards.

Add a field

Every field is described once, in the field registry. Say you want a vatNumber on companies.

  1. Column. In apps/backend/prisma/schema/crm.prisma, add it to CrmCompany, then run pnpm prisma migrate dev:

    prisma
    vatNumber String @default("") @map("vat_number") @db.VarChar(40)
  2. Schema. In packages/enums/src/crm/schemas.ts, add it to companyFields and to the CrmCompanyRecord type:

    ts
    vatNumber: text(40),
  3. Registry. In packages/enums/src/crm/fields.ts, add one entry to COMPANY_FIELDS:

    ts
    { key: "vatNumber", type: "text", icon: "Building2", group: "business", editable: true, sortable: true, filterable: true, defaultVisible: false, width: 160, csv: ["vat", "vat number"] },
  4. Label. TypeScript now points at fieldLabels in features/crm/utils/fields.ts, in the admin app. Add the label and its message key to apps/admin/messages/en.json. Return the new column from toCompanyRecord in the backend's mappers/records.ts.

The table column, its inline editor, the filter and sort menus, saved views, CSV import and export, the record panel, and the API's sort and filter whitelist all pick it up from the registry entry.

What is not in it

  • A CRM per organization, or anything your users can see. It is admin only.
  • Mobile app screens.
  • A link between CRM companies and the organizations that sign up to your product.
  • Workflows and automations.
  • Custom objects or fields added at runtime. Fields are added in code, as above.
  • Email and calendar sync.
  • Currency conversion. Each deal has a currency, and totals add amounts as stored.

After installing

bash
cd apps/backend
pnpm prisma generate
pnpm prisma migrate dev

Then sign in and open /companies. The first visit creates a default "Sales" pipeline.