Build a Marketplace
Create a two-sided marketplace with user auth, listings, and split payments
What You'll Build
A working marketplace where sellers can list products, buyers can purchase, and payments automatically split between you (platform fee) and the seller.
- Multi-user auth with seller and buyer roles
- Product listing creation and management
- Stripe Connect split payments
- Deployed and production-ready
Prerequisites
- Node.js 18+ installed
- Experience with React and TypeScript
- A Stripe account with Connect enabled
- About half a day of focused time
Architecture
Next.js handles the frontend and API layer. Clerk provides user authentication with role-based access (buyer vs seller). Supabase stores listings, orders, and user profiles. Stripe Connect handles payments - buyers pay, Stripe splits the funds between seller and your platform automatically.
Scaffold the Next.js app with Clerk
~15 minCreate a Next.js project and integrate Clerk for authentication.
- Create a new Next.js project:
npx create-next-app@latest marketplace --typescript --tailwind --app - Install Clerk:
npm install @clerk/nextjs - Create a Clerk application at clerk.com and get your API keys
- Add Clerk keys to
.env.local - Wrap your app with
in the root layout - Add Clerk middleware to protect routes
npx create-next-app@latest marketplace --typescript --tailwind --app
cd marketplace
npm install @clerk/nextjs
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_...
CLERK_SECRET_KEY=sk_...
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
and components instead of building forms from scratch.Set up the Supabase database schema
~20 minCreate the database tables for listings, orders, and seller profiles.
- Create a Supabase project and install
@supabase/supabase-js - Create a
sellerstable (user_id, stripe_account_id, store_name, approved) - Create a
listingstable (seller_id, title, description, price, images, status) - Create an
orderstable (buyer_id, listing_id, amount, stripe_payment_id, status) - Enable Row Level Security on all tables
- Write RLS policies: sellers can CRUD their own listings, buyers can read all active listings
CREATE TABLE sellers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id TEXT UNIQUE NOT NULL,
stripe_account_id TEXT,
store_name TEXT NOT NULL,
approved BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE listings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
seller_id UUID REFERENCES sellers(id),
title TEXT NOT NULL,
description TEXT,
price INTEGER NOT NULL,
images TEXT[],
status TEXT DEFAULT 'active',
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
buyer_id TEXT NOT NULL,
listing_id UUID REFERENCES listings(id),
amount INTEGER NOT NULL,
stripe_payment_id TEXT,
status TEXT DEFAULT 'pending',
created_at TIMESTAMPTZ DEFAULT now()
);
Set up Stripe Connect for split payments
~30 minEnable Stripe Connect so sellers can receive payments with your platform taking a fee.
- Enable Connect in your Stripe Dashboard under Connect → Settings
- Choose "Express" onboarding (Stripe handles seller KYC)
- Install Stripe:
npm install stripe - Create an API route to generate seller onboarding links
- Create a webhook handler for
account.updatedevents to track seller verification - Set your platform fee percentage (e.g. 10%)
import Stripe from 'stripe'
import { NextResponse } from 'next/server'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
export async function POST(req: Request) {
const { sellerId } = await req.json()
const account = await stripe.accounts.create({ type: 'express' })
const link = await stripe.accountLinks.create({
account: account.id,
refresh_url: `${process.env.NEXT_PUBLIC_URL}/seller/onboard`,
return_url: `${process.env.NEXT_PUBLIC_URL}/seller/dashboard`,
type: 'account_onboarding'
})
// Save account.id to the seller's record in Supabase
return NextResponse.json({ url: link.url })
}
Build the marketplace checkout
~30 minCreate a checkout flow that splits payments between your platform and the seller.
- Create a checkout API route that creates a Stripe Checkout Session with
payment_intent_data.transfer_data - Set the
application_fee_amountto your platform fee (e.g. 10% of the listing price) - Set
transfer_data.destinationto the seller's Stripe Connect account ID - Build a product detail page with a "Buy Now" button that calls your checkout API
- Create success and cancel pages for post-checkout redirects
const session = await stripe.checkout.sessions.create({
mode: 'payment',
line_items: [{
price_data: {
currency: 'usd',
product_data: { name: listing.title },
unit_amount: listing.price
},
quantity: 1
}],
payment_intent_data: {
application_fee_amount: Math.round(listing.price * 0.10),
transfer_data: { destination: seller.stripe_account_id }
},
success_url: `${process.env.NEXT_PUBLIC_URL}/orders/{CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.NEXT_PUBLIC_URL}/listings/${listing.id}`
})
Deploy to Vercel
~15 minDeploy your marketplace with all environment variables configured.
- Push to GitHub and import into Vercel
- Add all environment variables (Clerk, Supabase, Stripe keys)
- Configure your Stripe webhook endpoint for production
- Test a full buyer flow: browse → buy → seller gets paid
- Test a full seller flow: onboard → list product → receive payment
🎉 You're Done!
A working marketplace where sellers can list products, buyers can purchase, and payments automatically split between you (platform fee) and the seller.
Want this built for you?
Get a step-by-step checklist, setup order, and the exact config for every tool in this guide. Or let me build it for you.
Get the checklist → Want this built for you?