Build a multi-step lead capture tool with V0 using Next.js, Supabase, and shadcn/ui. Features conditional form logic, automatic lead scoring, a Kanban pipeline board, and embeddable form pages — all with server-side scoring to keep your rules private. Takes about 1-2 hours.
What you're building
Marketing teams spend thousands on lead generation tools like HubSpot and Typeform. A custom-built lead capture system gives you full control over the form experience, scoring logic, and pipeline management without per-seat pricing.
V0 generates the multi-step form UI, pipeline Kanban board, and scoring API from prompts. Connect Supabase via the Connect panel for the database, and use Server Components for the pipeline dashboard. The form builder and Kanban board use client components for interactivity.
The architecture uses public form pages generated with generateStaticParams for fast loading, an API route for lead submission and scoring, Supabase for data storage, and a Kanban pipeline dashboard for managing leads through stages.
Final result
A complete lead generation tool with multi-step capture forms, automatic scoring, a Kanban pipeline board, and UTM parameter tracking.
Tech stack
Prerequisites
- A V0 account (Premium or higher recommended)
- A Supabase project (free tier works — connect via V0's Connect panel)
- Your lead qualification criteria (what makes a lead hot, warm, or cold)
- Form fields you want to capture (company size, budget, timeline, etc.)
Build steps
Set up the database schema for forms, leads, and pipeline
Create the Supabase schema with tables for form definitions, lead submissions, pipeline stages, and lead-stage assignments. The forms table stores step definitions and scoring rules as JSONB.
1// Paste this prompt into V0's AI chat:2// Build a lead generation tool. Create a Supabase schema:3// 1. forms: id (uuid PK), name (text), slug (text UNIQUE), steps (jsonb), scoring_rules (jsonb), redirect_url (text), owner_id (uuid FK to auth.users)4// 2. leads: id (uuid PK), form_id (uuid FK to forms), email (text), name (text), company (text), responses (jsonb), score (int DEFAULT 0), status (text DEFAULT 'new'), source (text), utm_params (jsonb), created_at (timestamptz)5// 3. pipeline_stages: id (uuid PK), name (text), position (int), color (text)6// 4. lead_stage: lead_id (uuid FK to leads), stage_id (uuid FK to pipeline_stages), moved_at (timestamptz), moved_by (uuid FK to auth.users)7// Add RLS policies. Seed pipeline_stages with: New, Contacted, Qualified, Proposal, Won, Lost.8// Generate SQL and TypeScript types.Pro tip: Store scoring_rules as JSONB in the forms table — this lets you configure different scoring logic per form without changing code.
Expected result: Supabase is connected with all tables created, pipeline stages seeded, and RLS policies active.
Build the multi-step lead capture form
Create the public-facing form page that walks visitors through multiple steps with conditional logic. Each step shows different fields based on previous answers. UTM parameters from the URL are captured automatically.
1// Paste this prompt into V0's AI chat:2// Create a multi-step lead form at app/f/[slug]/page.tsx.3// Requirements:4// - Fetch the form definition by slug from Supabase (static generation with generateStaticParams)5// - Parse the steps jsonb to render each step's fields dynamically6// - Use shadcn/ui Input, Select, RadioGroup based on field type from the steps config7// - Show a Progress bar at the top showing step X of Y8// - Navigation: 'Next' Button advances, 'Back' Button goes back9// - On final step, show a 'Submit' Button that POSTs to /api/leads with all responses + UTM params from URL searchParams10// - After submit, redirect to the form's redirect_url or show a thank-you Card11// - Capture utm_source, utm_medium, utm_campaign, utm_content from the page URL automatically12// - Make it responsive and mobile-friendly with max-w-lg centered layoutExpected result: The public form page renders dynamic multi-step forms with progress tracking, field validation, and UTM parameter capture.
Create the server-side lead scoring engine
Build the API route that receives lead submissions, calculates a score based on the form's scoring rules, assigns a tier (hot/warm/cold), and stores the lead. Scoring logic runs server-side so it never leaks to visitors.
1import { createClient } from '@supabase/supabase-js'2import { NextRequest, NextResponse } from 'next/server'34const supabase = createClient(5 process.env.SUPABASE_URL!,6 process.env.SUPABASE_SERVICE_ROLE_KEY!7)89function calculateScore(10 responses: Record<string, string>,11 rules: { field: string; value: string; points: number }[]12): number {13 let score = 014 for (const rule of rules) {15 if (responses[rule.field] === rule.value) {16 score += rule.points17 }18 }19 return score20}2122function getTier(score: number): string {23 if (score >= 80) return 'hot'24 if (score >= 40) return 'warm'25 return 'cold'26}2728export async function POST(req: NextRequest) {29 const { form_id, email, name, company, responses, source, utm_params } =30 await req.json()3132 const { data: form } = await supabase33 .from('forms')34 .select('scoring_rules')35 .eq('id', form_id)36 .single()3738 const score = form?.scoring_rules39 ? calculateScore(responses, form.scoring_rules)40 : 04142 const { data: lead, error } = await supabase43 .from('leads')44 .insert({45 form_id,46 email,47 name,48 company,49 responses,50 score,51 status: getTier(score),52 source: source || 'direct',53 utm_params: utm_params || {},54 })55 .select()56 .single()5758 if (error) {59 return NextResponse.json({ error: error.message }, { status: 500 })60 }6162 // Assign to 'New' pipeline stage63 const { data: newStage } = await supabase64 .from('pipeline_stages')65 .select('id')66 .eq('name', 'New')67 .single()6869 if (newStage) {70 await supabase.from('lead_stage').insert({71 lead_id: lead.id,72 stage_id: newStage.id,73 moved_at: new Date().toISOString(),74 })75 }7677 return NextResponse.json({ success: true, lead_id: lead.id })78}Expected result: Lead submissions are scored server-side based on configurable rules and automatically assigned to the 'New' pipeline stage.
Build the Kanban pipeline dashboard
Create the pipeline management page showing leads organized by stage in a Kanban board. Each lead card shows name, company, score badge, and source. Leads can be moved between stages via a Server Action.
1// Paste this prompt into V0's AI chat:2// Create a pipeline Kanban board at app/leads/page.tsx.3// Requirements:4// - Fetch all pipeline_stages ordered by position, with leads in each stage5// - Display as horizontal columns (one per stage) with the stage name and count as header6// - Each lead shows as a shadcn/ui Card: name, company, email, score Badge (hot=red, warm=yellow, cold=blue), source, created_at7// - Clicking a lead Card opens a Sheet sidebar with full details: all form responses, score breakdown, stage history timeline, and notes8// - Add a Select dropdown on each lead to move it to a different pipeline stage (Server Action updates lead_stage)9// - Add filters at the top: search Input, score tier Select (hot/warm/cold), source Select, date range10// - Show total lead counts and conversion rate (Won / total) in summary Cards above the board11// - Mobile layout: stack columns vertically with collapsible sectionsPro tip: Use V0's Vars tab to store webhook URLs for pushing leads to external CRMs like HubSpot or Salesforce. Add the webhook call in the lead submission API route after scoring.
Expected result: The pipeline page shows a Kanban board with leads organized by stage, score badges, and a detail Sheet sidebar.
Add the form builder and deploy
Create an interface for building and editing lead capture forms with step configuration, field types, and scoring rules. Then deploy to production.
1// Paste this prompt into V0's AI chat:2// Create a form builder at app/forms/[id]/edit/page.tsx.3// Requirements:4// - Fetch the form by ID, show current steps and fields5// - Each step is a Card with: step title Input, list of fields below6// - Each field has: label Input, type Select (text/email/select/radio/number), options Textarea (for select/radio, comma-separated), required Switch7// - Add Field Button appends a new field to the current step8// - Add Step Button appends a new step9// - Scoring Rules section: for each select/radio field, show a Table where you assign points to each option value10// - Save Button uses a Server Action to update the form's steps and scoring_rules jsonb11// - Preview Button opens the form in a new tab at /f/[slug]12// - Use shadcn/ui Accordion for collapsible steps, Switch for required toggle, and Separator between sectionsExpected result: The form builder lets you configure multi-step forms with field types, scoring rules, and a live preview link. The app is deployed to Vercel.
Complete code
1import { createClient } from '@supabase/supabase-js'2import { NextRequest, NextResponse } from 'next/server'34const supabase = createClient(5 process.env.SUPABASE_URL!,6 process.env.SUPABASE_SERVICE_ROLE_KEY!7)89interface ScoringRule {10 field: string11 value: string12 points: number13}1415function calculateScore(16 responses: Record<string, string>,17 rules: ScoringRule[]18): number {19 let score = 020 for (const rule of rules) {21 if (responses[rule.field] === rule.value) {22 score += rule.points23 }24 }25 return score26}2728export async function POST(req: NextRequest) {29 const body = await req.json()30 const { form_id, email, name, company, responses, utm_params } = body3132 if (!form_id || !email) {33 return NextResponse.json({ error: 'Missing fields' }, { status: 400 })34 }3536 const { data: form } = await supabase37 .from('forms')38 .select('scoring_rules')39 .eq('id', form_id)40 .single()4142 const score = form?.scoring_rules43 ? calculateScore(responses, form.scoring_rules)44 : 04546 const tier = score >= 80 ? 'hot' : score >= 40 ? 'warm' : 'cold'4748 const { data: lead, error } = await supabase49 .from('leads')50 .insert({51 form_id, email, name, company,52 responses, score, status: tier,53 source: utm_params?.utm_source || 'direct',54 utm_params: utm_params || {},55 })56 .select('id')57 .single()5859 if (error) {60 return NextResponse.json({ error: error.message }, { status: 500 })61 }6263 return NextResponse.json({ success: true, lead_id: lead.id, score, tier })64}Customization ideas
CRM webhook integration
Add a webhook call in the lead submission API that sends new leads to HubSpot, Salesforce, or any CRM via their REST API. Store webhook URLs in the Vars tab.
Email notification on hot leads
Send an instant email alert via Resend when a lead scores as 'hot', including the lead's name, company, score, and a link to the pipeline dashboard.
A/B testing for form variants
Create multiple form variants with different step orders or questions and randomly assign visitors to measure which version generates higher-scoring leads.
Lead enrichment via Clearbit
After capturing a lead's email, call the Clearbit API to auto-populate company name, industry, employee count, and revenue data.
Common pitfalls
Pitfall: Running the scoring engine on the client side
How to avoid: Run the scoring logic in the API route (server-side). The client only submits responses; the server fetches rules from the database, calculates the score, and stores the result.
Pitfall: Not capturing UTM parameters from the form URL
How to avoid: Extract utm_source, utm_medium, utm_campaign from the page URL searchParams and include them in the lead submission payload. Store them in the utm_params jsonb column.
Pitfall: Making the public form page a client component unnecessarily
How to avoid: Use generateStaticParams to statically generate form pages by slug. Only the interactive step navigation and form submission need 'use client' — extract them as a child component.
Pitfall: Using NEXT_PUBLIC_ prefix for the service role key
How to avoid: Store SUPABASE_SERVICE_ROLE_KEY in V0's Vars tab without any prefix. Only use it in API routes.
Best practices
- Run lead scoring server-side to keep your qualification criteria private — never expose scoring rules to visitors
- Use generateStaticParams for public form pages so they load instantly from the CDN edge
- Store scoring rules as JSONB in the forms table so you can configure different scoring per form without code changes
- Capture UTM parameters automatically from the form URL for accurate source attribution
- Use V0's Vars tab to store webhook URLs for CRM integrations — this keeps credentials out of your code
- Use Server Components for the pipeline dashboard and client components only for the interactive Kanban board and form steps
- Add a unique index on (lead_id, stage_id) in lead_stage to prevent duplicate stage assignments
- Use V0's Design Mode (Option+D) to adjust the form styling, Kanban card layout, and Badge colors without spending credits
AI prompts to try
Copy these prompts to build this project faster.
I'm building a lead generation tool with Next.js App Router and Supabase. I need a server-side lead scoring engine. The scoring rules are stored as JSONB in the forms table: an array of {field, value, points} objects. When a lead submits their responses, I need to match their answers against the rules, sum the points, and assign a tier (hot >= 80, warm >= 40, cold < 40). Please write the TypeScript function and the API route at app/api/leads/route.ts.
Create a Kanban pipeline board component for managing leads. It should have horizontal columns (one per pipeline stage), each with a header showing stage name, color indicator, and lead count. Lead cards show name, company, score Badge, and source. Clicking a card opens a Sheet with full details. Each card has a Select dropdown to move to a different stage via a Server Action. Make the pipeline responsive — stack vertically on mobile.
Frequently asked questions
How does the lead scoring work?
Each form has scoring_rules stored as JSONB — an array of {field, value, points} objects. When a lead submits, the API route matches their responses against the rules, sums the points, and assigns a tier: hot (80+), warm (40-79), or cold (below 40). The scoring logic runs server-side so it is never visible to form visitors.
Can I embed the form on my existing website?
Yes. The form page at /f/[slug] is a standalone page that works in an iframe. Add an iframe tag pointing to your deployed URL with the form slug. UTM parameters pass through the URL for source tracking.
Do I need a paid V0 plan?
Premium ($20/month) is recommended. The lead generation tool has multiple complex pages (multi-step form, Kanban board, form builder) that require several prompts to build.
Can I push leads to my existing CRM?
Yes. Add a webhook call in the lead submission API route that sends the lead data to HubSpot, Salesforce, or any CRM with a REST API. Store the webhook URL in V0's Vars tab to keep it out of your code.
How do I track which marketing channel generates the best leads?
The form automatically captures UTM parameters (utm_source, utm_medium, utm_campaign) from the URL. You can filter leads by source in the pipeline dashboard and compare average scores across channels.
How do I deploy the lead generation tool?
Click Share in V0, then Publish to Production. The public form pages are statically generated for fast loading. Set SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY in the Vars tab before deploying.
Can RapidDev help build a custom lead generation tool?
Yes. RapidDev has built over 600 apps including lead capture systems with advanced scoring, CRM integrations, and marketing automation workflows. Book a free consultation to discuss your requirements.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation