How to Build a Multi-Vendor Marketplace with Stripe Connect
Multi-vendor marketplaces — platforms like Etsy, Airbnb, Uber, and Fiverr — are among the most valuable business models on the internet. They connect buyers with sellers, take a commission on each transaction, and scale by onboarding more vendors.
Building a marketplace platform from scratch is significantly more complex than building a single-vendor e-commerce site. You need vendor onboarding, commission management, split payments, dispute resolution, and a review system. Stripe Connect handles the payment complexity, and a marketplace starter kit provides the application infrastructure.
What Makes Marketplace Development Complex
A multi-vendor marketplace requires features that single-vendor platforms do not:
- Vendor registration and onboarding with KYC verification
- Product or service listing management per vendor
- Commission calculation and automatic splitting
- Delayed payouts with escrow-like protection
- Stripe Connect for marketplace payments
- Vendor dashboard with earnings analytics
- Review and rating system for trust
The Multi-Vendor Marketplace Starter template includes all of this pre-built.
Stripe Connect Architecture
Stripe Connect provides two main integration patterns:
- Destination charges: You charge the customer, take your commission, and send the remainder to the vendor
- Separate charges and transfers: Each party is charged separately, and you transfer funds between accounts
For most marketplaces, destination charges with platform fees work best:
// lib/stripe/marketplace.ts
import { stripe } from './server'
export async function createMarketplacePayment(
amount: number,
vendorStripeAccountId: string,
platformFee: number,
description: string
) {
const payment = await stripe.paymentIntents.create({
amount: Math.round(amount * 100),
currency: 'usd',
description,
application_fee_amount: Math.round(platformFee * 100),
transfer_data: {
destination: vendorStripeAccountId,
},
})
return payment
}Vendor Onboarding with Stripe Connect
// app/api/vendor/onboard/route.ts
import { NextResponse } from 'next/server'
import { stripe } from '@/lib/stripe/server'
import { auth } from '@/lib/auth'
import { prisma } from '@/lib/prisma'
export async function GET() {
const session = await auth()
if (!session?.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const user = await prisma.user.findUnique({
where: { id: session.user.id },
select: { stripeConnectId: true, email: true },
})
let accountId = user?.stripeConnectId
if (!accountId) {
// Create a Stripe Connect Express account
const account = await stripe.accounts.create({
type: 'express',
email: user!.email,
capabilities: {
transfers: { requested: true },
},
metadata: { userId: session.user.id },
})
accountId = account.id
await prisma.user.update({
where: { id: session.user.id },
data: { stripeConnectId: account.id },
})
}
// Generate onboarding link
const accountLink = await stripe.accountLinks.create({
account: accountId,
refresh_url: `${process.env.NEXT_PUBLIC_APP_URL}/vendor/onboard`,
return_url: `${process.env.NEXT_PUBLIC_APP_URL}/vendor/dashboard`,
type: 'account_onboarding',
})
return NextResponse.json({ url: accountLink.url })
}Product Listing with Vendor Association
// app/actions/create-listing.ts
'use server'
import { supabase } from '@/lib/supabase'
import { auth } from '@/lib/auth'
import { revalidatePath } from 'next/cache'
export async function createListing(formData: FormData) {
const session = await auth()
if (!session?.user) throw new Error('Unauthorized')
const user = await prisma.user.findUnique({
where: { id: session.user.id },
select: { isVendor: true },
})
if (!user?.isVendor) throw new Error('Not a vendor')
const name = formData.get('name') as string
const description = formData.get('description') as string
const price = parseFloat(formData.get('price') as string)
const category = formData.get('category') as string
const { error } = await supabase.from('products').insert({
vendor_id: session.user.id,
name,
description,
price,
category,
status: 'active',
})
if (error) throw new Error('Failed to create listing')
revalidatePath('/vendor/products')
}Commission Management
// lib/marketplace/commissions.ts
export interface CommissionConfig {
platformFeePercent: number
platformFeeFixed: number
vendorPayoutSchedule: 'daily' | 'weekly' | 'monthly'
minimumPayoutAmount: number
}
export const defaultCommission: CommissionConfig = {
platformFeePercent: 10, // 10% platform fee
platformFeeFixed: 0.50, // $0.50 fixed fee
vendorPayoutSchedule: 'weekly',
minimumPayoutAmount: 25, // $25 minimum payout
}
export function calculateFees(
itemPrice: number,
config: CommissionConfig = defaultCommission
) {
const percentFee = itemPrice * (config.platformFeePercent / 100)
const totalFee = percentFee + config.platformFeeFixed
const vendorEarnings = itemPrice - totalFee
return {
platformFee: Math.round(totalFee * 100) / 100,
vendorEarnings: Math.round(vendorEarnings * 100) / 100,
itemPrice,
}
}Vendor Dashboard with Earnings
// app/vendor/dashboard/page.tsx
import { auth } from '@/lib/auth'
import { prisma } from '@/lib/prisma'
import { redirect } from 'next/navigation'
import { EarningsSummary } from './EarningsSummary'
import { RecentOrders } from './RecentOrders'
export default async function VendorDashboard() {
const session = await auth()
if (!session?.user) redirect('/login')
const earnings = await prisma.order_item.aggregate({
_sum: { price: true },
where: {
product: { vendor_id: session.user.id },
order: { status: 'completed' },
},
})
const totalSales = await prisma.order_item.count({
where: {
product: { vendor_id: session.user.id },
order: { status: 'completed' },
},
})
return (
<div className="mx-auto max-w-6xl px-4 py-8">
<h1 className="text-2xl font-bold">Vendor Dashboard</h1>
<div className="mt-6 grid gap-6 sm:grid-cols-3">
<div className="rounded-xl border bg-white p-6">
<p className="text-sm text-gray-500">Total Earnings</p>
<p className="mt-2 text-3xl font-bold text-green-600">
${(earnings._sum.price ?? 0).toFixed(2)}
</p>
</div>
<div className="rounded-xl border bg-white p-6">
<p className="text-sm text-gray-500">Total Sales</p>
<p className="mt-2 text-3xl font-bold">{totalSales}</p>
</div>
<div className="rounded-xl border bg-white p-6">
<p className="text-sm text-gray-500">Available for Payout</p>
<p className="mt-2 text-3xl font-bold text-brand-600">
${(earnings._sum.price ?? 0 * 0.9).toFixed(2)}
</p>
</div>
</div>
<RecentOrders vendorId={session.user.id} />
</div>
)
}The Bottom Line
Multi-vendor marketplaces are powerful but complex. Stripe Connect handles the payment infrastructure, and a marketplace starter kit provides vendor onboarding, commission management, and dashboards. The Multi-Vendor Marketplace Starter template gives you everything you need to launch your platform.
Related Template
Try this production-ready starter kit to build your project faster.
Shop All Products
Wireless Headphones
Leather Backpack
Smart Watch
Sunglasses
E-Commerce Starter
Build your online store the smart way
Ready to Build?
Get started with our production-ready starter kits and ship your project faster.
Browse Starter Kits