Skip to main content
RapidDev - Software Development Agency

How to Build Inventory system with V0

Build a warehouse inventory management system with V0 using Next.js, Supabase, and shadcn/ui. Track stock levels across warehouses, log inventory movements, and get low-stock alerts — all with atomic database transactions to prevent race conditions. Takes about 1-2 hours to complete.

What you'll build

  • Stock overview dashboard with sortable DataTable showing quantities, SKUs, and warehouse locations
  • Low-stock alert system with color-coded Badge indicators (green/yellow/red) based on reorder points
  • Goods receipt form for logging incoming inventory with warehouse and product selection
  • Inventory movement history with filtering by movement type (in, out, transfer, adjustment)
  • Atomic stock updates using Supabase RPC transactions to prevent race conditions
  • Product detail page with full movement history timeline and current stock per warehouse
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate12 min read1-2 hoursV0 Premium or higherApril 2026RapidDev Engineering Team
TL;DR

Build a warehouse inventory management system with V0 using Next.js, Supabase, and shadcn/ui. Track stock levels across warehouses, log inventory movements, and get low-stock alerts — all with atomic database transactions to prevent race conditions. Takes about 1-2 hours to complete.

What you're building

Small business owners lose thousands of dollars to stockouts and overstock because they track inventory in spreadsheets. A proper inventory system gives you real-time visibility into stock levels, automates low-stock alerts, and creates an audit trail of every movement.

V0 makes building this dramatically faster. You describe your inventory tables and UI in the chat, and V0 generates Next.js pages with shadcn/ui DataTables, forms, and filters. Connect Supabase via the Connect panel for instant database setup, and you have a working system on Vercel without touching a terminal.

The architecture uses Next.js App Router with Server Components for the stock overview (zero JavaScript on initial load), a Supabase RPC function for atomic stock updates, and client components only where interactivity is needed — forms, filters, and the warehouse picker.

Final result

A fully functional inventory management system with stock tracking across multiple warehouses, movement logging, low-stock alerts, and goods receipt processing.

Tech stack

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

Prerequisites

  • A V0 account (Premium or higher recommended for multiple prompts)
  • A Supabase project (free tier works — connect via V0's Connect panel)
  • Basic understanding of inventory concepts (SKUs, stock levels, movements)
  • A list of products and warehouse locations you want to track

Build steps

1

Set up the project and database schema with Supabase

Open V0 and create a new project. Use the Connect panel to add Supabase — this auto-provisions your database URL and keys. Then prompt V0 to create the full inventory schema with products, warehouses, inventory levels, and movements.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build an inventory management system. Create a Supabase schema with these tables:
3// 1. products: id (uuid PK), sku (text UNIQUE), name (text), description (text), category (text), unit_price (numeric), reorder_point (int), created_at (timestamptz)
4// 2. warehouses: id (uuid PK), name (text), location (text)
5// 3. inventory: id (uuid PK), product_id (uuid FK to products), warehouse_id (uuid FK to warehouses), quantity (int), last_counted_at (timestamptz)
6// 4. movements: id (uuid PK), product_id (uuid FK to products), warehouse_id (uuid FK to warehouses), movement_type (text CHECK IN 'in','out','transfer','adjustment'), quantity (int), reference (text), created_at (timestamptz), created_by (uuid FK to auth.users)
7// Add RLS policies so authenticated users can read all inventory but only insert movements.
8// Generate the SQL migration and TypeScript types.

Pro tip: Use V0's Connect panel to wire up Supabase in one click — it auto-populates your NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY in the Vars tab.

Expected result: Supabase is connected, all four tables are created with proper foreign keys, and RLS policies are in place.

2

Build the stock overview dashboard with DataTable

Prompt V0 to create the main inventory page showing all products with their current stock levels, warehouse locations, and status indicators. This page uses Server Components for fast initial load and a client-side DataTable for sorting and filtering.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Create an inventory dashboard at app/inventory/page.tsx.
3// Requirements:
4// - Fetch all products joined with inventory levels grouped by warehouse
5// - Display in a shadcn/ui DataTable with columns: SKU, Product Name, Category, Warehouse, Quantity, Reorder Point, Status
6// - Status column shows a Badge: green 'In Stock' if quantity > reorder_point, yellow 'Low Stock' if quantity <= reorder_point and > 0, red 'Out of Stock' if quantity = 0
7// - Add column sorting (click headers) and a search Input to filter by SKU or product name
8// - Add a Select dropdown to filter by warehouse
9// - Add a Button 'Receive Stock' that links to /inventory/receive
10// - Show summary Cards at the top: Total Products, Low Stock Items, Out of Stock Items

Expected result: The inventory page displays a sortable, filterable DataTable with color-coded stock status Badges and summary cards at the top.

3

Create the goods receipt form for incoming stock

Build the stock receiving page where users log incoming inventory. The form needs product selection, warehouse picker, quantity input, and a reference field. On submit, it creates a movement record and updates the stock level atomically.

app/api/movements/route.ts
1import { createClient } from '@supabase/supabase-js'
2import { NextRequest, NextResponse } from 'next/server'
3
4const supabase = createClient(
5 process.env.SUPABASE_URL!,
6 process.env.SUPABASE_SERVICE_ROLE_KEY!
7)
8
9export async function POST(req: NextRequest) {
10 const { product_id, warehouse_id, quantity, reference } = await req.json()
11
12 if (!product_id || !warehouse_id || !quantity || quantity <= 0) {
13 return NextResponse.json(
14 { error: 'Invalid input' },
15 { status: 400 }
16 )
17 }
18
19 const { data, error } = await supabase.rpc('receive_stock', {
20 p_product_id: product_id,
21 p_warehouse_id: warehouse_id,
22 p_quantity: quantity,
23 p_reference: reference || 'Manual receipt',
24 })
25
26 if (error) {
27 return NextResponse.json({ error: error.message }, { status: 500 })
28 }
29
30 return NextResponse.json({ success: true, data })
31}

Pro tip: Use Supabase RPC functions for atomic operations. Create a PostgreSQL function that inserts the movement AND updates the inventory quantity in a single transaction — this prevents race conditions when multiple users receive stock simultaneously.

Expected result: Submitting the receipt form creates a movement record and updates the inventory quantity atomically. The stock overview reflects the new quantity immediately.

4

Build the product detail page with movement history

Create a dynamic page that shows a single product's details, current stock per warehouse, and a full history of all movements. Use Next.js dynamic routes with a Server Component for the initial data fetch.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Create a product detail page at app/inventory/[productId]/page.tsx.
3// Requirements:
4// - Fetch the product by ID from Supabase with all inventory records joined by warehouse
5// - Show product info in a Card: name, SKU, category, unit price, reorder point
6// - Show stock per warehouse in a Table with columns: Warehouse, Quantity, Status Badge, Last Counted
7// - Below that, show a movement history Table with columns: Date, Type (Badge colored by type), Quantity, Reference, User
8// - Movement types: 'in' = green Badge, 'out' = red Badge, 'transfer' = blue Badge, 'adjustment' = yellow Badge
9// - Add a Dialog button for quick stock adjustment (select warehouse, enter quantity +/-, add reference note)
10// - Use Tabs to switch between 'Stock Levels' and 'Movement History'

Expected result: The product detail page shows stock levels per warehouse with status badges, and a full movement history with color-coded movement type indicators.

5

Create the Supabase RPC function for atomic stock updates

This is the critical piece that prevents race conditions. Create a PostgreSQL function in Supabase that handles both the movement insert and quantity update in a single transaction, using row-level locking to handle concurrent updates safely.

supabase/migrations/receive_stock.sql
1-- Run this in Supabase SQL Editor
2CREATE OR REPLACE FUNCTION receive_stock(
3 p_product_id uuid,
4 p_warehouse_id uuid,
5 p_quantity int,
6 p_reference text DEFAULT 'Manual receipt'
7)
8RETURNS json AS $$
9DECLARE
10 v_inventory_id uuid;
11 v_new_quantity int;
12BEGIN
13 -- Upsert inventory record with row lock
14 INSERT INTO inventory (product_id, warehouse_id, quantity, last_counted_at)
15 VALUES (p_product_id, p_warehouse_id, p_quantity, now())
16 ON CONFLICT (product_id, warehouse_id)
17 DO UPDATE SET
18 quantity = inventory.quantity + p_quantity,
19 last_counted_at = now()
20 RETURNING id, quantity INTO v_inventory_id, v_new_quantity;
21
22 -- Insert movement record
23 INSERT INTO movements (product_id, warehouse_id, movement_type, quantity, reference, created_at)
24 VALUES (p_product_id, p_warehouse_id, 'in', p_quantity, p_reference, now());
25
26 RETURN json_build_object('inventory_id', v_inventory_id, 'new_quantity', v_new_quantity);
27END;
28$$ LANGUAGE plpgsql SECURITY DEFINER;

Expected result: The RPC function is created in Supabase. Calling supabase.rpc('receive_stock', {...}) atomically updates stock and logs the movement.

6

Add low-stock alert notifications and deploy

Add a visual alert section to the dashboard showing all products below their reorder point. Then configure environment variables and publish to Vercel for your team to use.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Add a low-stock alerts section to the inventory dashboard.
3// Requirements:
4// - Query products where inventory.quantity <= products.reorder_point
5// - Display as a list of AlertDialog-style Cards with: product name, SKU, current quantity, reorder point, warehouse
6// - Each card has a 'Reorder' Button that pre-fills the receive stock form
7// - Sort by urgency (out of stock first, then lowest percentage of reorder point)
8// - Show the count in a red Badge on a 'Low Stock Alerts' tab
9// - If no low-stock items, show a success message with a check icon

Pro tip: Use Design Mode (Option+D) to adjust the alert card colors, spacing, and badge styles without spending any credits. Visual tweaks are free in V0.

Expected result: The dashboard shows a Low Stock Alerts tab with urgent items sorted by severity. Clicking Reorder navigates to the receive form pre-filled with the product details.

Complete code

app/api/movements/route.ts
1import { createClient } from '@supabase/supabase-js'
2import { NextRequest, NextResponse } from 'next/server'
3
4const supabase = createClient(
5 process.env.SUPABASE_URL!,
6 process.env.SUPABASE_SERVICE_ROLE_KEY!
7)
8
9export async function POST(req: NextRequest) {
10 const { product_id, warehouse_id, quantity, movement_type, reference } =
11 await req.json()
12
13 if (!product_id || !warehouse_id || !quantity || quantity <= 0) {
14 return NextResponse.json({ error: 'Invalid input' }, { status: 400 })
15 }
16
17 const rpcName =
18 movement_type === 'out' ? 'dispatch_stock' : 'receive_stock'
19
20 const { data, error } = await supabase.rpc(rpcName, {
21 p_product_id: product_id,
22 p_warehouse_id: warehouse_id,
23 p_quantity: quantity,
24 p_reference: reference || `Manual ${movement_type}`,
25 })
26
27 if (error) {
28 return NextResponse.json({ error: error.message }, { status: 500 })
29 }
30
31 return NextResponse.json({ success: true, data })
32}
33
34export async function GET(req: NextRequest) {
35 const { searchParams } = new URL(req.url)
36 const productId = searchParams.get('product_id')
37
38 let query = supabase
39 .from('movements')
40 .select('*, products(name, sku), warehouses(name)')
41 .order('created_at', { ascending: false })
42 .limit(50)
43
44 if (productId) {
45 query = query.eq('product_id', productId)
46 }
47
48 const { data, error } = await query
49
50 if (error) {
51 return NextResponse.json({ error: error.message }, { status: 500 })
52 }
53
54 return NextResponse.json({ data })
55}

Customization ideas

Barcode scanner integration

Add a barcode scanning component using the device camera and a library like quagga2 to quickly look up products by SKU during receiving and stocktaking.

Automated reorder notifications

Set up a Vercel Cron Job that checks stock levels daily and sends email alerts via Resend when products drop below their reorder points.

Stock transfer between warehouses

Add a transfer workflow that decrements stock from one warehouse and increments at another in a single atomic transaction with a transfer reference linking both movements.

Inventory valuation report

Build a reporting page that calculates total inventory value (quantity x unit_price) per warehouse and category, displayed with Recharts bar and pie charts.

Common pitfalls

Pitfall: Updating inventory quantity with separate INSERT and UPDATE queries

How to avoid: Use a Supabase RPC function (PostgreSQL stored procedure) that inserts the movement and updates the quantity in a single transaction with row-level locking.

Pitfall: Using NEXT_PUBLIC_ prefix for the SUPABASE_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 (server-side). Use NEXT_PUBLIC_SUPABASE_ANON_KEY for client-side reads.

Pitfall: Not adding a unique constraint on product_id + warehouse_id in the inventory table

How to avoid: Add a UNIQUE constraint on (product_id, warehouse_id) in the inventory table and use ON CONFLICT in your upsert queries.

Pitfall: Allowing negative inventory quantities without validation

How to avoid: Add a CHECK constraint (quantity >= 0) on the inventory table and validate available quantity in the RPC function before processing outbound movements.

Best practices

  • Use Supabase RPC functions for all stock-changing operations to guarantee atomic updates and prevent data inconsistencies
  • Enable RLS on all tables and scope policies to authenticated users — the anon key should only allow read access to non-sensitive data
  • Use V0's Design Mode (Option+D) to adjust DataTable column widths, Badge colors, and card spacing without spending credits
  • Add a unique composite index on (product_id, warehouse_id) in the inventory table for fast lookups and to enforce one row per product per warehouse
  • Store movement references (PO numbers, transfer IDs) in every movement record for a complete audit trail
  • Use Server Components for the stock overview page — the DataTable data loads server-side with zero client JavaScript overhead
  • Set reorder_point per product rather than a global threshold, since different products have different lead times and demand patterns
  • Use V0's prompt queuing to build the stock overview, product detail, and receipt form in sequence without waiting between prompts

AI prompts to try

Copy these prompts to build this project faster.

ChatGPT Prompt

I'm building a warehouse inventory management system with Next.js App Router and Supabase. I need a PostgreSQL function that atomically handles stock receiving — it should insert a movement record and update (or create) the inventory quantity in one transaction with proper row locking. The tables are: products (id, sku, name), warehouses (id, name), inventory (product_id, warehouse_id, quantity), movements (product_id, warehouse_id, movement_type, quantity, reference). Please write the SQL function and the TypeScript API route that calls it.

Build Prompt

Create a Supabase RPC function called receive_stock that takes product_id, warehouse_id, quantity, and reference as parameters. It should upsert into the inventory table (insert if new product-warehouse combo, update quantity if exists) and insert into the movements table — all in one transaction. Then build a Next.js API route at app/api/movements/route.ts that calls this function. Include proper error handling for invalid inputs and insufficient stock on outbound movements.

Frequently asked questions

How does the inventory system prevent two people from updating the same stock at once?

The system uses a Supabase RPC function (PostgreSQL stored procedure) that runs the movement insert and quantity update in a single database transaction. PostgreSQL's row-level locking ensures that concurrent updates are serialized — one completes before the other starts, preventing incorrect stock counts.

Can I track inventory across multiple warehouses?

Yes. The schema uses a separate warehouses table and the inventory table stores quantity per product-warehouse combination. The stock overview DataTable can be filtered by warehouse using the Select dropdown, and product detail pages show stock levels at each location.

Do I need a paid V0 plan for this project?

You can start on the free tier, but Premium ($20/month) is recommended because the inventory system requires multiple prompts for the dashboard, product detail page, receipt form, and movement history. The free tier's limited credits may run out mid-build.

How do I add barcode scanning to the inventory system?

Add a client component that uses the device camera with a library like quagga2 or html5-qrcode. When a barcode is scanned, look up the product by SKU in Supabase and pre-fill the receipt or adjustment form. Import the library dynamically with next/dynamic and ssr: false since it requires browser APIs.

Can I get email alerts when stock is low?

Yes. Set up a Vercel Cron Job that runs daily, queries products where inventory.quantity is at or below reorder_point, and sends an email summary via the Resend API. Store RESEND_API_KEY in the Vars tab without a NEXT_PUBLIC_ prefix.

How do I deploy the inventory system to production?

Click Share in the top-right corner of V0, then Publish, then Publish to Production. Your app deploys to Vercel in 30-60 seconds. Alternatively, connect to GitHub via the Git panel — V0 creates a branch, you merge the PR, and Vercel auto-deploys from main.

Can RapidDev help build a custom inventory system?

Yes. RapidDev has built over 600 apps including inventory and warehouse management systems with barcode scanning, multi-location transfers, and ERP integrations. Book a free consultation to discuss your specific requirements and get a production-ready system.

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.