Skip to main content
RapidDev - Software Development Agency

How to Build CRM system with V0

Build a pipeline-based CRM system with V0 using Next.js and Supabase. You'll get a Kanban deal board with intuitive drag-and-drop, contact management, activity timeline, weighted revenue forecasting, and global search — all in about 1-2 hours without any local setup.

What you'll build

  • Kanban-style deal board with drag-and-drop between pipeline stages using @hello-pangea/dnd
  • Contact management with searchable Table, quick-add Sheet, and tag-based filtering
  • Deal detail page with activity timeline logging calls, emails, meetings, and notes
  • Pipeline revenue forecasting based on deal values and stage probability weights
  • Optimistic drag-and-drop updates with Server Action backend and automatic rollback on error
  • Global search across contacts and deals using shadcn/ui Command palette
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate10 min read1-2 hoursV0 Premium or higherApril 2026RapidDev Engineering Team
TL;DR

Build a pipeline-based CRM system with V0 using Next.js and Supabase. You'll get a Kanban deal board with intuitive drag-and-drop, contact management, activity timeline, weighted revenue forecasting, and global search — all in about 1-2 hours without any local setup.

What you're building

Every sales team needs a CRM to track contacts, manage deals through pipeline stages, and forecast revenue. But most CRM software is bloated, expensive, and designed for enterprise teams. Founders and small teams need something simple that just works.

V0 generates the complete CRM interface from prompts — Kanban boards, contact tables, deal detail pages, and activity timelines. Supabase via the Connect panel provides the database with RLS for multi-user isolation and real-time updates for collaborative use.

The architecture uses Next.js Server Components for contact lists and deal pages (fast server-rendered data fetching), a Client Component for the Kanban board with @hello-pangea/dnd for drag-and-drop, Server Actions for all mutations (deal stage updates, contact creation, activity logging), and Supabase with RLS policies scoped by owner_id for multi-tenant isolation.

Final result

A complete CRM with a Kanban deal board, contact management, activity timeline, pipeline revenue forecasting, and global search — all behind Supabase Auth with multi-user data isolation.

Tech stack

V0AI Code Generator
Next.jsFull-Stack Framework
Tailwind CSSStyling
shadcn/uiComponent Library
SupabaseDatabase
Supabase AuthAuth

Prerequisites

  • A V0 account (Premium plan recommended for multi-page builds)
  • A Supabase project (free tier works — connect via V0's Connect panel)
  • Understanding of your sales pipeline stages (e.g., Lead, Qualified, Proposal, Won, Lost)

Build steps

1

Set up the CRM database schema with pipeline stages

Create a new V0 project, connect Supabase via the Connect panel, and create the contacts, pipelines, deals, and activities tables. The pipeline stages are stored as a JSONB column for easy customization.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build a CRM system with Supabase. Create these tables:
3// 1. contacts: id (uuid PK), owner_id (uuid FK to auth.users), company (text), name (text), email (text), phone (text), source (text), tags (text[]), created_at (timestamptz)
4// 2. pipelines: id (uuid PK), org_id (uuid FK), name (text), stages (jsonb DEFAULT '[{"name":"Lead","order":0},{"name":"Qualified","order":1},{"name":"Proposal","order":2},{"name":"Won","order":3},{"name":"Lost","order":4}]')
5// 3. deals: id (uuid PK), contact_id (uuid FK), pipeline_id (uuid FK), stage (text), title (text), value (numeric), expected_close (date), owner_id (uuid FK), created_at (timestamptz), updated_at (timestamptz)
6// 4. activities: id (uuid PK), deal_id (uuid FK), user_id (uuid FK), type (text CHECK in 'call','email','meeting','note'), content (text), created_at (timestamptz)
7// Add RLS policies scoped by owner_id matching auth.uid().

Pro tip: Use the Connect panel to add Supabase in two clicks — it auto-provisions NEXT_PUBLIC_SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY in the Vars tab. Then use V0 to generate the SQL migration.

Expected result: Supabase is connected with CRM tables created, pipeline stages configured as JSONB, and RLS policies restricting data by owner.

2

Build the Kanban deal board with drag-and-drop

Create the main deals page with a Kanban board layout. Each pipeline stage is a column, and deal Cards can be dragged between columns. Stage changes update the deal in Supabase via a Server Action with optimistic UI.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build a Kanban deal board at app/deals/page.tsx.
3// Requirements:
4// - 'use client' component using @hello-pangea/dnd for drag-and-drop
5// - Columns for each pipeline stage (Lead, Qualified, Proposal, Won, Lost)
6// - Deal Cards in each column showing: title, value (formatted as currency), contact name, expected close date, owner Avatar
7// - Drag a Card between columns to change its stage
8// - On drop, optimistically update the local state and call a Server Action to update deals.stage in Supabase
9// - If the Server Action fails, revert the optimistic state and show an error toast
10// - Column headers show the count of deals and total value
11// - Add a "+" Button on each column header to create a new deal in that stage
12// - Use shadcn/ui Card for deal tiles, Badge for stage labels, Avatar for owners
13// - Add Tabs to switch between Kanban and list Table views

Expected result: The deal board shows pipeline stages as columns with draggable deal Cards. Dropping a Card in a new column updates the stage optimistically.

3

Create the contact management page with search and filtering

Build the contacts page with a searchable, filterable Table. Users can add new contacts via a Sheet slide-over, view contact details, and filter by tags and source.

app/contacts/page.tsx
1import { createClient } from '@supabase/supabase-js'
2import { ContactTable } from '@/components/contact-table'
3import { Button } from '@/components/ui/button'
4import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
5
6const supabase = createClient(
7 process.env.SUPABASE_URL!,
8 process.env.SUPABASE_SERVICE_ROLE_KEY!
9)
10
11export default async function ContactsPage() {
12 const { data: contacts } = await supabase
13 .from('contacts')
14 .select('*, deals(id, title, value, stage)')
15 .order('created_at', { ascending: false })
16
17 const totalContacts = contacts?.length ?? 0
18 const withDeals = contacts?.filter((c) => c.deals?.length > 0).length ?? 0
19
20 return (
21 <div className="space-y-6">
22 <div className="flex items-center justify-between">
23 <h1 className="text-3xl font-bold">Contacts</h1>
24 <Button>Add Contact</Button>
25 </div>
26 <div className="grid gap-4 md:grid-cols-3">
27 <Card>
28 <CardHeader><CardTitle>Total Contacts</CardTitle></CardHeader>
29 <CardContent className="text-3xl font-bold">{totalContacts}</CardContent>
30 </Card>
31 <Card>
32 <CardHeader><CardTitle>With Active Deals</CardTitle></CardHeader>
33 <CardContent className="text-3xl font-bold">{withDeals}</CardContent>
34 </Card>
35 </div>
36 <ContactTable contacts={contacts ?? []} />
37 </div>
38 )
39}

Pro tip: Use Design Mode (Option+D) to adjust the Table column widths, contact Card spacing, and Badge colors for tags without spending any credits.

Expected result: The contacts page shows a searchable Table with stats Cards. The Sheet slide-over allows adding new contacts without navigating away.

4

Build the deal detail page with activity timeline

Create the deal detail page showing all deal information and a chronological activity timeline. Sales reps can log calls, emails, meetings, and notes. Activities are displayed in a vertical timeline with type-specific icons.

app/actions/crm.ts
1'use server'
2
3import { createClient } from '@supabase/supabase-js'
4import { revalidatePath } from 'next/cache'
5
6const supabase = createClient(
7 process.env.SUPABASE_URL!,
8 process.env.SUPABASE_SERVICE_ROLE_KEY!
9)
10
11export async function updateDealStage(dealId: string, stage: string) {
12 const { error } = await supabase
13 .from('deals')
14 .update({ stage, updated_at: new Date().toISOString() })
15 .eq('id', dealId)
16
17 if (error) throw new Error(error.message)
18 revalidatePath('/deals')
19}
20
21export async function logActivity(
22 dealId: string,
23 userId: string,
24 type: 'call' | 'email' | 'meeting' | 'note',
25 content: string
26) {
27 const { error } = await supabase.from('activities').insert({
28 deal_id: dealId,
29 user_id: userId,
30 type,
31 content,
32 })
33
34 if (error) throw new Error(error.message)
35 revalidatePath(`/deals/${dealId}`)
36}
37
38export async function createDeal(
39 contactId: string,
40 pipelineId: string,
41 title: string,
42 value: number,
43 stage: string,
44 ownerId: string
45) {
46 const { error } = await supabase.from('deals').insert({
47 contact_id: contactId,
48 pipeline_id: pipelineId,
49 title,
50 value,
51 stage,
52 owner_id: ownerId,
53 })
54
55 if (error) throw new Error(error.message)
56 revalidatePath('/deals')
57}

Expected result: The deal detail page shows deal info, contact details, and a timeline of activities. Users can log new activities with type selection and content.

5

Add pipeline revenue forecasting and global search

Build a forecasting view that calculates expected revenue based on deal values and stage probability weights. Add a global search using shadcn/ui Command palette to search across contacts and deals.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build two features:
3// 1. Revenue forecast at app/forecast/page.tsx:
4// - Assign probability weights to pipeline stages: Lead=10%, Qualified=30%, Proposal=60%, Won=100%, Lost=0%
5// - Calculate weighted pipeline value: SUM(deal.value * stage_probability)
6// - Show summary Cards: Total Pipeline Value, Weighted Forecast, Deals Closing This Month
7// - BarChart (Recharts) showing deal values by stage
8// - Table of deals sorted by expected_close date with stage Badge and weighted value
9// 2. Global search using shadcn/ui Command (Cmd+K):
10// - Search across contacts (by name, company, email) and deals (by title)
11// - Show results grouped by type with icons
12// - Navigate to contact or deal detail page on selection
13// - Use Supabase full-text search with to_tsvector on searchable columns

Expected result: The forecast page shows weighted pipeline revenue with charts. Cmd+K opens a global search across contacts and deals.

Complete code

app/actions/crm.ts
1'use server'
2
3import { createClient } from '@supabase/supabase-js'
4import { revalidatePath } from 'next/cache'
5
6const supabase = createClient(
7 process.env.SUPABASE_URL!,
8 process.env.SUPABASE_SERVICE_ROLE_KEY!
9)
10
11export async function updateDealStage(dealId: string, stage: string) {
12 const { error } = await supabase
13 .from('deals')
14 .update({ stage, updated_at: new Date().toISOString() })
15 .eq('id', dealId)
16 if (error) throw new Error(error.message)
17 revalidatePath('/deals')
18}
19
20export async function logActivity(
21 dealId: string,
22 userId: string,
23 type: 'call' | 'email' | 'meeting' | 'note',
24 content: string
25) {
26 const { error } = await supabase.from('activities').insert({
27 deal_id: dealId,
28 user_id: userId,
29 type,
30 content,
31 })
32 if (error) throw new Error(error.message)
33 revalidatePath(`/deals/${dealId}`)
34}
35
36export async function createContact(
37 ownerId: string,
38 name: string,
39 company: string,
40 email: string,
41 phone: string,
42 source: string
43) {
44 const { error } = await supabase.from('contacts').insert({
45 owner_id: ownerId,
46 name,
47 company,
48 email,
49 phone,
50 source,
51 })
52 if (error) throw new Error(error.message)
53 revalidatePath('/contacts')
54}
55
56export async function createDeal(
57 contactId: string,
58 pipelineId: string,
59 title: string,
60 value: number,
61 stage: string,
62 ownerId: string
63) {
64 const { error } = await supabase.from('deals').insert({
65 contact_id: contactId,
66 pipeline_id: pipelineId,
67 title,
68 value,
69 stage,
70 owner_id: ownerId,
71 })
72 if (error) throw new Error(error.message)
73 revalidatePath('/deals')
74}

Customization ideas

Add email integration

Connect to Gmail or Outlook via API to log emails to deal activities automatically based on contact email address matching.

Add deal automation rules

Create automation rules that trigger when a deal enters a stage (e.g., send a proposal email when deal moves to Proposal stage) using Supabase triggers and Server Actions.

Add reporting dashboard

Build a reporting page with charts for deals won/lost over time, average deal cycle length, win rate by source, and top-performing sales reps.

Add team collaboration

Add org_id to all tables and update RLS policies to allow team members within the same organization to view and collaborate on deals and contacts.

Common pitfalls

Pitfall: Not using optimistic updates for drag-and-drop stage changes

How to avoid: Update the local state immediately on drop using React state, then fire the Server Action in the background. If the action fails, revert the state and show an error toast.

Pitfall: Querying Supabase without owner_id filtering

How to avoid: Always include .eq('owner_id', userId) in Supabase queries in addition to RLS policies. RLS is defense-in-depth, not a replacement for proper query scoping.

Pitfall: Storing pipeline stages as separate database rows instead of JSONB

How to avoid: Store stages as a JSONB array in the pipelines table. This makes retrieval a single query and reordering is a simple array manipulation.

Best practices

  • Use @hello-pangea/dnd for Kanban drag-and-drop — it is the maintained fork of react-beautiful-dnd and works with React Server Components
  • Store pipeline stages as JSONB for easy customization and ordering without complex table joins
  • Use Server Components for contact lists and deal tables to keep database queries server-side
  • Use RLS policies scoped by owner_id matching auth.uid() for multi-tenant data isolation
  • Use Design Mode (Option+D) to adjust Kanban column widths, Card styling, and Badge colors without spending credits
  • Log every customer interaction as an activity to build a complete relationship history
  • Use revalidatePath after every mutation to keep the Kanban board and contact list current
  • Implement global search with Supabase full-text search and shadcn/ui Command for a professional UX

AI prompts to try

Copy these prompts to build this project faster.

ChatGPT Prompt

I'm building a pipeline CRM with Next.js App Router and Supabase. I need a Kanban deal board with drag-and-drop, contact management, activity logging, and revenue forecasting. Help me design the schema with JSONB pipeline stages and RLS policies scoped by owner_id.

Build Prompt

Build a Kanban board component with @hello-pangea/dnd that renders pipeline stages as droppable columns and deals as draggable Cards. On drag end, optimistically update the local state to move the Card to the new column, then call a Server Action to update the deal's stage in Supabase. If the action fails, revert the Card to its original column and show a toast error.

Frequently asked questions

What is the best drag-and-drop library for a V0 Kanban board?

@hello-pangea/dnd is recommended — it is the actively maintained fork of react-beautiful-dnd with full React 18 support. V0 can install it via the project dependencies and generate the DragDropContext, Droppable, and Draggable components.

How do I handle multi-user access in the CRM?

Use Supabase RLS policies scoped by owner_id matching auth.uid(). Each user only sees their own contacts and deals. For team access, add an org_id column and update policies to allow team members within the same organization.

What V0 plan do I need for a CRM system?

V0 Premium is recommended because the CRM requires multiple complex pages (Kanban board, contacts, deal detail, forecast), drag-and-drop components, and several Server Actions.

Can I customize the pipeline stages?

Yes. Stages are stored as a JSONB array in the pipelines table. Add a settings page where users can add, remove, and reorder stages by modifying the JSONB array via a Server Action.

How do I deploy the CRM?

Click Share then Publish to Production in V0 for instant Vercel deployment. All data is stored in Supabase with RLS ensuring multi-user data isolation.

Can I import existing contacts from a spreadsheet?

Yes. Add a CSV import page similar to the budgeting tool pattern — upload a CSV, parse it with papaparse, map columns to contact fields, and bulk-insert into Supabase.

Can RapidDev help build a custom CRM?

Yes. RapidDev has built 600+ apps including CRMs with email integration, deal automation, multi-team pipelines, and advanced reporting. Book a free consultation to discuss your CRM requirements.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help building your app?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.