# How to Build Insurance claims tool with V0

- Tool: v0
- Difficulty: Advanced
- Compatibility: V0 Premium
- Last updated: April 2026

## TL;DR

Build an insurance claims management tool with V0 using Next.js, Supabase for claims data, and a PostgreSQL state machine for status transitions. You'll create a claims submission wizard, adjuster review queue, status timeline, and management analytics — all in about 2-4 hours without touching a terminal.

## Before you start

- A V0 account (Premium recommended for the complex multi-page app)
- A Supabase project with Storage enabled for claim documents
- Basic understanding of insurance claims (submit, review, approve/deny, pay)
- Familiarity with role-based access (policyholder, adjuster, manager)

## Step-by-step guide

### 1. Set up the project and claims database schema

Open V0 and create a new project. Connect Supabase via the Connect panel. Create the schema for policyholders, claims, documents, notes, and status tracking.

```
// Paste this prompt into V0's AI chat:
// Build an insurance claims tool. Create a Supabase schema with:
// 1. policyholders: id (uuid PK), user_id (uuid FK to auth.users), policy_number (text unique), full_name (text), email (text), phone (text), address (text), policy_type (text check in 'auto','home','health','life','business'), policy_start (date), policy_end (date), coverage_limit_cents (bigint)
// 2. claims: id (uuid PK), policyholder_id (uuid FK to policyholders), claim_number (text unique), type (text check in 'auto_collision','auto_theft','home_damage','home_theft','health_medical','health_dental','life','business_liability'), description (text), incident_date (date), incident_location (text), estimated_amount_cents (bigint), approved_amount_cents (bigint), status (text default 'submitted' check in 'submitted','under_review','investigation','approved','partially_approved','denied','paid','closed'), priority (text default 'normal' check in 'low','normal','high','urgent'), assigned_adjuster_id (uuid FK to auth.users nullable), submitted_at (timestamptz), resolved_at (timestamptz)
// 3. claim_documents: id (uuid PK), claim_id (uuid FK to claims), title (text), file_url (text), document_type (text check in 'photo','police_report','medical_record','receipt','estimate','other'), uploaded_by (uuid FK to auth.users), uploaded_at (timestamptz)
// 4. claim_notes: id (uuid PK), claim_id (uuid FK to claims), author_id (uuid FK to auth.users), content (text), is_internal (boolean default true), created_at (timestamptz)
// 5. claim_status_log: id (uuid PK), claim_id (uuid FK to claims), from_status (text), to_status (text), changed_by (uuid FK to auth.users), reason (text), created_at (timestamptz)
// Add RLS: policyholders see their own claims, adjusters see assigned claims, managers see all.
```

> Pro tip: Queue the policyholder submission flow, adjuster review interface, and manager analytics dashboard as three separate prompt sequences using V0's prompt queuing feature.

**Expected result:** Database schema created with 5 tables, RLS policies for role-based access, and status tracking for complete claim audit trails.

### 2. Build the multi-step claim submission wizard

Create the claim submission page with a step-by-step wizard for entering incident details, uploading supporting documents, and reviewing before submission.

```
// Paste this prompt into V0's AI chat:
// Build a claim submission wizard at app/claims/new/page.tsx as a 'use client' component.
// Requirements:
// - Stepper showing 4 steps: Incident Details → Documents → Review → Confirmation
// - Step 1 (Incident Details): Select for claim type, Input for incident_date (date picker), Textarea for description, Input for incident_location, Input for estimated_amount (currency formatted)
// - Step 2 (Documents): File upload zone accepting multiple files, Select for document_type per file (photo, police_report, medical_record, receipt, estimate, other), Preview thumbnails for uploaded images
// - Step 3 (Review): Summary Card showing all entered data with edit buttons per section
// - Step 4 (Confirmation): success message with claim_number display
// - Zod validation: incident_date <= today, estimated_amount <= coverage_limit, at least one document required for auto/home claims
// - Server Action that: generates claim_number (CLM-{year}-{sequence}), inserts claim, uploads documents to Supabase Storage bucket 'claim-documents', inserts claim_documents rows, creates initial claim_status_log entry
// - Navigate to /claims/[id] after submission
// - Use Card for step content areas and Badge for document type labels
```

**Expected result:** Policyholders can submit claims through a guided 4-step wizard with document uploads, validation, and automatic claim number generation.

### 3. Build the claim detail page with status timeline

Create the claim detail page showing the full claim lifecycle with status timeline, documents, internal notes, and action buttons.

```
// Paste this prompt into V0's AI chat:
// Build claim detail at app/claims/[id]/page.tsx.
// Requirements:
// - Header: claim_number, type Badge, status Badge (color-coded), priority Badge
// - Status timeline: vertical Stepper showing each status_log entry with timestamp, author, and reason
// - Tabs component with sections:
//   1. Overview: incident details Card, estimated vs approved amount, assigned adjuster
//   2. Documents: gallery grid of uploaded documents with download Button per file, document_type Badge, "Upload Additional" Button
//   3. Notes: threaded conversation of claim_notes, Textarea to add new note, Switch for is_internal (visible only to adjusters/managers)
//   4. History: full status_log Table with from→to transition, changed_by, reason, timestamp
// - Action buttons (role-dependent):
//   - Adjuster: "Approve", "Deny", "Request Investigation" buttons that open Dialog with reason Textarea and approved_amount Input (for approve)
//   - Manager: "Reassign" Button with adjuster Select
// - AlertDialog for deny action requiring a reason
// - Server Actions for status transitions, note creation, and document upload
```

**Expected result:** The claim detail page shows a complete timeline of status changes, organized document gallery, internal notes thread, and role-appropriate action buttons.

### 4. Create the state machine for status transitions

Build the PostgreSQL function that validates and enforces the claims status transition rules, preventing invalid status changes.

```
// Paste this prompt into V0's AI chat:
// Build the status transition system:
// 1. Create a PostgreSQL function transition_claim_status(claim_id uuid, new_status text, changed_by uuid, reason text):
//    - Fetch current status with FOR UPDATE lock
//    - Validate against state machine rules:
//      submitted → under_review only
//      under_review → investigation, approved, partially_approved, denied
//      investigation → approved, partially_approved, denied
//      approved → paid
//      partially_approved → paid
//      denied → closed
//      paid → closed
//    - If invalid transition, RAISE EXCEPTION 'Invalid status transition from X to Y'
//    - Insert into claim_status_log (from_status, to_status, changed_by, reason)
//    - Update claims.status = new_status
//    - If new_status in ('approved','denied','paid','closed'), set resolved_at = now()
//    - Return the updated claim
//
// 2. Create API route at app/api/claims/[id]/status/route.ts:
//    - PATCH handler that validates role permissions and calls the RPC
//    - Adjusters can transition to under_review, investigation, approved, denied
//    - Managers can do all transitions
//    - Policyholders cannot change status
//    - Use Zod to validate input: new_status, reason (required for deny)
```

> Pro tip: The FOR UPDATE row lock in the PostgreSQL function prevents race conditions — even if two adjusters try to transition the same claim simultaneously, only one will succeed.

**Expected result:** The state machine enforces valid transitions at the database level. Invalid status changes raise exceptions even if called directly, ensuring claims follow the proper workflow.

### 5. Build the adjuster queue and management dashboard

Create the claims list with filtering, the adjuster's work queue, and the management analytics dashboard.

```
// Paste this prompt into V0's AI chat:
// Build three pages:
// 1. app/claims/page.tsx — claims list:
//    - Table with columns: claim_number, type Badge, policyholder name, status Badge, priority Badge, submitted date, assigned adjuster
//    - Column sorting on submitted_at and priority
//    - Select filters: status, priority, claim type
//    - Search Input for claim_number or policyholder name
//    - Click row to navigate to /claims/[id]
//
// 2. app/page.tsx — role-based dashboard:
//    - Policyholder view: my claims list with status indicators, "File New Claim" Button
//    - Adjuster view: assigned claims queue sorted by priority DESC, unassigned claims needing pickup
//    - Manager view: KPI Cards (total claims, avg processing days, total payouts), claims volume BarChart by month, status distribution PieChart
//
// 3. app/admin/page.tsx — management analytics:
//    - Recharts BarChart: claims volume by month with status stacking
//    - LineChart: average processing time trend
//    - Cards: total payouts this month, denial rate, adjuster workload distribution
//    - Table: top 10 highest-value open claims
//    - Select for time period and claim type filtering
//    - Wrap all charts in 'use client' components
```

**Expected result:** Adjusters see their work queue sorted by priority. Managers see analytics with claims volume, processing times, and payout trends. Policyholders see their own claims.

## Complete code example

File: `app/api/claims/[id]/status/route.ts`

```typescript
import { createClient } from '@/lib/supabase/server'
import { NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'

const schema = z.object({
  newStatus: z.enum([
    'under_review', 'investigation', 'approved',
    'partially_approved', 'denied', 'paid', 'closed',
  ]),
  reason: z.string().min(1).max(1000),
  approvedAmount: z.number().int().nonnegative().optional(),
})

export async function PATCH(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params
  const supabase = await createClient()
  const { data: { user } } = await supabase.auth.getUser()
  if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })

  const body = await request.json()
  const { newStatus, reason, approvedAmount } = schema.parse(body)

  if (approvedAmount && newStatus === 'approved') {
    await supabase
      .from('claims')
      .update({ approved_amount_cents: approvedAmount })
      .eq('id', id)
  }

  const { data, error } = await supabase.rpc('transition_claim_status', {
    claim_id: id,
    new_status: newStatus,
    changed_by: user.id,
    reason,
  })

  if (error) {
    if (error.message.includes('Invalid status transition')) {
      return NextResponse.json({ error: error.message }, { status: 422 })
    }
    return NextResponse.json({ error: error.message }, { status: 500 })
  }

  return NextResponse.json({ success: true, claim: data })
}
```

## Common mistakes

- **Allowing arbitrary status changes without validation** — Without a state machine, claims can jump from submitted directly to paid, skipping the review process and creating audit issues. Fix: Implement a PostgreSQL function that validates each transition against a defined state machine and raises an exception on invalid changes.
- **Not logging status transitions** — Without an audit trail, there's no record of who changed a claim's status or when, making disputes and compliance reviews impossible. Fix: Automatically insert into claim_status_log within the transition function, capturing from_status, to_status, changed_by, reason, and timestamp.
- **Storing monetary amounts as floats** — Floating-point arithmetic causes rounding errors in insurance payouts, leading to discrepancies in financial reports. Fix: Store all amounts as bigint in cents (estimated_amount_cents, approved_amount_cents) and format for display client-side.
- **Not restricting document access by role** — Without bucket RLS, any authenticated user can access another policyholder's sensitive documents like medical records. Fix: Configure Supabase Storage bucket RLS to restrict reads to the claim owner and assigned adjuster only.

## Best practices

- Implement status transitions as a PostgreSQL function with a state machine to enforce valid workflow at the database level.
- Log every status change in a claim_status_log table for complete audit trails required by insurance regulations.
- Store all monetary values as bigint in cents to avoid floating-point rounding errors in financial calculations.
- Use FOR UPDATE row locks in the transition function to prevent race conditions during concurrent claim processing.
- Configure Supabase Storage bucket RLS to restrict document access by role — policyholders see their own, adjusters see assigned claims.
- Use Clerk custom claims for role-based access (policyholder, adjuster, manager) with middleware.ts enforcement.
- Validate that estimated_amount does not exceed coverage_limit in the submission Server Action using Zod.

## Frequently asked questions

### Can I build this with the free V0 plan?

The core submission wizard and claims list fit within the free tier. V0 Premium is recommended for the full multi-page system with analytics dashboards.

### How does the status state machine work?

A PostgreSQL function validates each transition against a predefined map of allowed paths. For example, a claim in submitted status can only move to under_review. Attempting an invalid transition raises a database exception.

### What authentication should I use?

Clerk is recommended for role-based access. Set custom claims (policyholder, adjuster, manager) in the Clerk Dashboard metadata and enforce them in middleware.ts and Server Actions.

### How are documents secured?

Claim documents are stored in a Supabase Storage bucket with RLS policies restricting access to the claim owner and the assigned adjuster. Internal notes are filtered by role on the server side.

### Can I add email notifications?

Yes. Add a Resend integration that sends emails at each status transition. Call Resend from within the PostgreSQL transition function trigger or from the API route after a successful transition.

### How do I deploy?

Publish via V0's Share menu. Set SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, and Clerk credentials in the Vars tab. Configure the claim-documents Storage bucket and RLS policies.

### Can RapidDev help build a custom claims platform?

Yes. RapidDev has built 600+ apps including insurance platforms with fraud detection, SLA tracking, and regulatory compliance features. Book a free consultation to discuss your needs.

---

Source: https://www.rapidevelopers.com/how-to-build-v0/insurance-claims-tool
© RapidDev — https://www.rapidevelopers.com/how-to-build-v0/insurance-claims-tool
