Skip to main content
RapidDev - Software Development Agency

How to Build Property management system with V0

Build a landlord property management dashboard with V0 using Next.js and Supabase to track units, tenants, leases, rent payments, and maintenance requests. Features real-time occupancy rate calculations, rent roll tracking, and automated overdue payment reminders — all in about 1-2 hours.

What you'll build

  • Portfolio overview dashboard with occupancy rates and revenue metrics using shadcn/ui Cards
  • Property and unit management with status tracking (vacant/occupied/maintenance)
  • Tenant directory with lease details and contact information
  • Rent roll and payment tracking with shadcn/ui Table and status Badges
  • Maintenance request queue with priority levels and status workflow
  • Overdue payment reminder system via API route notifications
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 landlord property management dashboard with V0 using Next.js and Supabase to track units, tenants, leases, rent payments, and maintenance requests. Features real-time occupancy rate calculations, rent roll tracking, and automated overdue payment reminders — all in about 1-2 hours.

What you're building

Managing rental properties means juggling tenants, leases, payments, and maintenance across multiple units. Spreadsheets break down fast. A dedicated dashboard gives landlords and property managers a single view of their entire portfolio — occupancy rates, upcoming lease expirations, overdue payments, and open maintenance requests.

V0 makes building this practical by generating the dashboard pages, data tables, and management forms from prompts. Connect Supabase via the Connect panel for instant database provisioning, and use Supabase database views for computed metrics like occupancy rates.

The architecture uses Next.js App Router with Server Components for data-heavy pages (rent roll, tenant directory), Server Actions for all mutations (recording payments, updating maintenance status), API routes for notification endpoints, and Supabase with RLS ensuring landlords only see their own properties.

Final result

A complete property management system with a portfolio dashboard, unit and tenant management, rent tracking with payment history, maintenance request workflows, and automated overdue payment reminders.

Tech stack

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

Prerequisites

  • A V0 account (Premium recommended for multiple dashboard pages)
  • A Supabase project (free tier works — connect via V0's Connect panel)
  • A Clerk account for authentication (free tier works)
  • Basic understanding of property management (units, leases, rent cycles)

Build steps

1

Set up the project and property management schema

Open V0 and create a new project. Use the Connect panel to add Supabase and enable Clerk via Vercel Marketplace. Prompt V0 to create the full schema for properties, units, tenants, leases, payments, and maintenance.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build a property management system. Create a Supabase schema with:
3// 1. properties: id (uuid PK), owner_id (uuid FK), name (text), address (text), type (text check residential/commercial), units_count (integer), created_at (timestamptz)
4// 2. units: id (uuid PK), property_id (uuid FK), unit_number (text), bedrooms (integer), bathrooms (numeric), rent_amount (integer), status (text check vacant/occupied/maintenance), created_at (timestamptz)
5// 3. tenants: id (uuid PK), user_id (uuid nullable), first_name (text), last_name (text), email (text), phone (text), created_at (timestamptz)
6// 4. leases: id (uuid PK), unit_id (uuid FK), tenant_id (uuid FK), start_date (date), end_date (date), monthly_rent (integer), deposit (integer), status (text check active/expired/terminated), created_at (timestamptz)
7// 5. payments: id (uuid PK), lease_id (uuid FK), amount (integer), payment_date (date), method (text), status (text check paid/pending/overdue/partial), created_at (timestamptz)
8// 6. maintenance_requests: id (uuid PK), unit_id (uuid FK), tenant_id (uuid FK nullable), title (text), description (text), priority (text check low/medium/high/emergency), status (text check open/in_progress/completed), created_at (timestamptz)
9// RLS: owner_id = auth.uid() for all property-related queries.
10// Generate SQL migration and TypeScript types.

Pro tip: Use V0's Connect panel to provision Supabase with the full schema — prompt V0 with the table structure and it generates migrations automatically.

Expected result: Supabase is connected with all six tables created. RLS policies ensure landlords only see their own properties, units, tenants, leases, payments, and maintenance requests.

2

Build the portfolio overview dashboard

Create the main dashboard showing key metrics across all properties — total units, occupancy rate, monthly revenue, and overdue payments. Use Supabase database views for computed aggregations.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build a property management dashboard at app/properties/page.tsx.
3// Requirements:
4// - Protected by Clerk auth
5// - Top row of 4 metric Cards:
6// - Total Units (count of units), Occupied (count where status=occupied), Occupancy Rate (percentage), Monthly Revenue (sum of active lease monthly_rent)
7// - Property list below as Cards showing: property name, address, units count, occupancy Badge
8// - Each property Card is clickable → navigates to app/properties/[id]/page.tsx
9// - Use Tabs to switch between: All Properties, Residential, Commercial
10// - Use shadcn/ui Badge for property type (residential=blue, commercial=purple)
11// - Data fetched server-side using Supabase database views joining units and leases
12// - Server Components for all data fetching

Expected result: A dashboard with four metric Cards (total units, occupied, occupancy rate, revenue) and a filterable list of property Cards with occupancy Badges.

3

Create the rent roll and payment tracking page

Build a rent roll page showing all active leases with payment status. This is the core financial view for landlords — which tenants have paid, which are overdue, and the total expected vs. collected revenue.

app/payments/page.tsx
1import { createClient } from '@/lib/supabase/server'
2import { Card } from '@/components/ui/card'
3import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
4import { Badge } from '@/components/ui/badge'
5
6const statusColors = {
7 paid: 'default',
8 pending: 'secondary',
9 overdue: 'destructive',
10 partial: 'outline',
11} as const
12
13export default async function PaymentsPage() {
14 const supabase = await createClient()
15
16 const { data: leases } = await supabase
17 .from('leases')
18 .select(`
19 id, monthly_rent, status,
20 units!inner(unit_number, properties!inner(name)),
21 tenants!inner(first_name, last_name, email),
22 payments(amount, payment_date, status)
23 `)
24 .eq('status', 'active')
25 .order('created_at', { ascending: false })
26
27 const totalExpected = leases?.reduce((sum, l) => sum + l.monthly_rent, 0) ?? 0
28 const totalCollected = leases?.reduce((sum, l) => {
29 const paid = l.payments?.filter((p: { status: string }) => p.status === 'paid')
30 .reduce((s: number, p: { amount: number }) => s + p.amount, 0) ?? 0
31 return sum + paid
32 }, 0) ?? 0
33
34 return (
35 <div className="p-6 space-y-6">
36 <div className="grid grid-cols-3 gap-4">
37 <Card className="p-4">
38 <p className="text-sm text-muted-foreground">Expected</p>
39 <p className="text-2xl font-bold">${(totalExpected / 100).toLocaleString()}</p>
40 </Card>
41 <Card className="p-4">
42 <p className="text-sm text-muted-foreground">Collected</p>
43 <p className="text-2xl font-bold">${(totalCollected / 100).toLocaleString()}</p>
44 </Card>
45 <Card className="p-4">
46 <p className="text-sm text-muted-foreground">Outstanding</p>
47 <p className="text-2xl font-bold text-destructive">
48 ${((totalExpected - totalCollected) / 100).toLocaleString()}
49 </p>
50 </Card>
51 </div>
52
53 <Table>
54 <TableHeader>
55 <TableRow>
56 <TableHead>Property</TableHead>
57 <TableHead>Unit</TableHead>
58 <TableHead>Tenant</TableHead>
59 <TableHead>Rent</TableHead>
60 <TableHead>Status</TableHead>
61 </TableRow>
62 </TableHeader>
63 <TableBody>
64 {leases?.map((lease) => (
65 <TableRow key={lease.id}>
66 <TableCell>{(lease.units as any).properties.name}</TableCell>
67 <TableCell>{(lease.units as any).unit_number}</TableCell>
68 <TableCell>
69 {(lease.tenants as any).first_name} {(lease.tenants as any).last_name}
70 </TableCell>
71 <TableCell>${(lease.monthly_rent / 100).toLocaleString()}</TableCell>
72 <TableCell>
73 <Badge variant={statusColors[(lease.payments?.[0] as any)?.status ?? 'pending'] ?? 'secondary'}>
74 {(lease.payments?.[0] as any)?.status ?? 'pending'}
75 </Badge>
76 </TableCell>
77 </TableRow>
78 ))}
79 </TableBody>
80 </Table>
81 </div>
82 )
83}

Expected result: A rent roll page showing expected, collected, and outstanding revenue Cards plus a Table of all active leases with tenant names, rent amounts, and payment status Badges.

4

Build the maintenance request queue

Create a maintenance request page where tenants submit requests and landlords manage them through a status workflow (open → in progress → completed). Priority levels help triage emergency repairs.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build a maintenance request page at app/maintenance/page.tsx.
3// Requirements:
4// - Show all maintenance requests in a shadcn/ui Table
5// - Columns: unit number, tenant name, title, priority Badge (emergency=red, high=orange, medium=blue, low=gray), status Badge (open=yellow, in_progress=blue, completed=green), created_at
6// - Filter by status using shadcn/ui Tabs: All, Open, In Progress, Completed
7// - Filter by priority using Select dropdown
8// - Sort by priority (emergency first) then created_at
9// - Clicking a row opens a Dialog with full description and status update controls
10// - Status update: Select for new status, Textarea for notes, Button to save
11// - "New Request" Button opens Dialog with: Select for unit, Input for title, Textarea for description, Select for priority
12// - Server Actions: createMaintenanceRequest(), updateMaintenanceStatus()
13// - Use Server Components for data fetching

Pro tip: Use V0's Design Mode (Option+D) to visually adjust the maintenance table row heights, Badge colors, and Dialog widths for free.

Expected result: A maintenance queue with filterable Table, priority and status Badges, and a Dialog for updating request status. Emergency requests appear at the top.

5

Add the tenant onboarding and lease management flow

Build forms for adding new tenants and creating leases. Include a tenant directory page with search and the lease creation flow that links tenants to units.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build tenant and lease management:
3// 1. Tenant directory at app/tenants/page.tsx:
4// - Searchable Table of all tenants with first_name, last_name, email, phone, active lease unit
5// - "Add Tenant" Button opens Dialog with Input fields for name, email, phone
6// - Clicking a tenant row shows their lease history and payment history
7// - Server Action: createTenant()
8// 2. Lease creation at app/properties/[id]/page.tsx:
9// - In the property detail view, each unit Card has an "Add Lease" Button (only for vacant units)
10// - Dialog with: Select for tenant (from tenants table), Calendar for start_date and end_date, Input for monthly_rent and deposit
11// - Creating a lease automatically sets unit status to 'occupied'
12// - AlertDialog for lease termination ("This will mark the unit as vacant")
13// - Server Actions: createLease(), terminateLease()
14// 3. Both use Server Components for data fetching, 'use client' for interactive forms

Expected result: A tenant directory with search and add functionality, plus a lease creation Dialog on each vacant unit that links tenants to units and updates occupancy status.

Complete code

app/actions/properties.ts
1'use server'
2
3import { createClient } from '@/lib/supabase/server'
4import { auth } from '@clerk/nextjs/server'
5import { revalidatePath } from 'next/cache'
6
7export async function createUnit(
8 propertyId: string,
9 data: {
10 unit_number: string
11 bedrooms: number
12 bathrooms: number
13 rent_amount: number
14 }
15) {
16 const supabase = await createClient()
17
18 await supabase.from('units').insert({
19 property_id: propertyId,
20 ...data,
21 status: 'vacant',
22 })
23
24 revalidatePath(`/properties/${propertyId}`)
25}
26
27export async function recordPayment(
28 leaseId: string,
29 amount: number,
30 method: string
31) {
32 const supabase = await createClient()
33
34 await supabase.from('payments').insert({
35 lease_id: leaseId,
36 amount,
37 payment_date: new Date().toISOString().split('T')[0],
38 method,
39 status: 'paid',
40 })
41
42 revalidatePath('/payments')
43}
44
45export async function updateMaintenanceStatus(
46 requestId: string,
47 status: string
48) {
49 const supabase = await createClient()
50
51 await supabase
52 .from('maintenance_requests')
53 .update({ status })
54 .eq('id', requestId)
55
56 revalidatePath('/maintenance')
57}

Customization ideas

Add lease renewal reminders

Create a scheduled API route that checks for leases expiring within 60 days and sends email reminders to both landlord and tenant via Resend.

Build a tenant portal

Create a separate tenant-facing interface where tenants can view their lease, submit maintenance requests, and see payment history.

Add document management

Use Supabase Storage to attach lease agreements, inspection photos, and maintenance receipts to their respective records.

Integrate online rent payments

Add Stripe via Vercel Marketplace so tenants can pay rent online with automatic status updates when payments are confirmed via webhook.

Common pitfalls

Pitfall: Calculating occupancy rates client-side by fetching all units

How to avoid: Use Supabase database views that join units and leases server-side and return pre-computed occupancy rates. Only the aggregated number reaches the client.

Pitfall: Not updating unit status when creating or terminating a lease

How to avoid: In the createLease Server Action, update both the leases table AND the units table (status='occupied') in the same operation. Do the reverse in terminateLease.

Pitfall: Storing rent amounts as floating-point dollars

How to avoid: Store all monetary amounts as integers in cents. Display by dividing by 100. This eliminates rounding errors in calculations.

Best practices

  • Store all monetary amounts as integers in cents to avoid floating-point rounding errors
  • Use Supabase database views for computed metrics like occupancy rates and revenue projections
  • Use V0's Connect panel for provisioning Supabase with the full schema — prompt V0 with the table structure for auto-generated migrations
  • Set RLS policies ensuring owners only see their own properties (owner_id = auth.uid())
  • Use Server Components for all data-heavy pages (rent roll, tenant directory) to keep financial data server-side
  • Use V0's Design Mode (Option+D) to visually adjust dashboard Card spacing and Table row heights for free

AI prompts to try

Copy these prompts to build this project faster.

ChatGPT Prompt

I'm building a property management system with Next.js App Router and Supabase. Help me create a Supabase database view that calculates occupancy rates and monthly revenue per property. Join properties, units, and leases tables. Return property name, total units, occupied units, occupancy percentage, and sum of active lease monthly rents. Also write a view for upcoming lease expirations within 60 days.

Build Prompt

Create a property detail page showing all units in a grid of Cards. Each Card shows unit number, bedrooms/bathrooms, rent amount, and status Badge (vacant=green, occupied=blue, maintenance=orange). Vacant units have an 'Add Lease' Button that opens a Dialog with tenant Select, date pickers, and rent Input. Use a Server Action that creates the lease and updates the unit status to occupied.

Frequently asked questions

What V0 plan do I need for a property management system?

V0 Free works for the basic build, but Premium ($20/month) is recommended because the system has multiple complex pages (dashboard, rent roll, maintenance, tenants) that benefit from prompt queuing.

Can tenants access the system to submit maintenance requests?

The base build is landlord-focused. To add a tenant portal, create a separate set of pages with tenant-specific RLS policies that only show their own lease, payments, and the ability to create maintenance requests for their unit.

How do I handle multiple properties across different locations?

The schema supports multiple properties per owner. The dashboard shows aggregate metrics across all properties, and each property has its own detail page with unit-level management.

How do I deploy the property management system?

Click Share then Publish to Production in V0. Set CLERK_SECRET_KEY in the Vars tab without NEXT_PUBLIC_ prefix. Supabase credentials are auto-configured from the Connect panel.

Can I add online rent payment processing?

Yes. Add Stripe via Vercel Marketplace for one-click setup. Create a checkout route that generates a Stripe Payment Intent for the rent amount, and a webhook handler that updates the payment status to paid when confirmed.

Can RapidDev help build a custom property management platform?

Yes. RapidDev has built 600+ apps including property management systems with tenant portals, online payments, document management, and accounting integrations. Book a free consultation to discuss your needs.

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.