skills/business-operations/marketplace-building/SKILL.md
Launch a multi-vendor marketplace with seller onboarding, commission rules, automated payouts via Stripe Connect, and vendor dashboards
npx skillsauth add finsilabs/awesome-ecommerce-skills marketplace-buildingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
A multi-vendor marketplace lets independent sellers list products on your platform, collects payment from buyers, deducts your commission, and pays out the remainder to sellers. The key components are: seller onboarding with KYC verification, product listing management per seller, commission calculation, and automated payouts. For Shopify and WooCommerce merchants, purpose-built marketplace apps handle most of this — custom development is needed primarily for highly specific commission structures or white-label marketplace platforms.
| Platform | Recommended Tool | Why | |----------|-----------------|-----| | Shopify | Multi Vendor Marketplace by Webkul or BOLD Multi-Vendor | Webkul's app adds seller accounts, product management, commission rules, and a seller dashboard to Shopify without replacing the storefront | | WooCommerce | Dokan Multi-Vendor (most popular, 60K+ installs) or WC Vendors | Dokan is purpose-built for WooCommerce marketplaces with seller onboarding, commission management, payout requests, and a seller dashboard | | BigCommerce | Multi Vendor Marketplace by Webkul (BigCommerce version) | Webkul has a BigCommerce version of their marketplace app | | Custom / Headless | Build seller accounts + Stripe Connect for KYC and payouts | Stripe Connect handles KYC, bank account collection, and 1099-K tax forms — use it for any custom marketplace |
KYC (Know Your Customer) is required to verify seller identity before you can legally send them payments. Using Stripe Connect for this is strongly recommended — building it yourself is expensive and legally complex.
yourstore.com/seller/register)import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
// Create a Stripe Express account for a new seller and return the onboarding URL
async function onboardSeller(sellerId: string, sellerEmail: string): Promise<string> {
// Create the Stripe Express account
const account = await stripe.accounts.create({
type: 'express',
email: sellerEmail,
capabilities: {
transfers: { requested: true },
},
});
// Store the Stripe account ID on the seller record
await db.sellers.update(sellerId, { stripe_account_id: account.id });
// Generate the onboarding link (valid for 24 hours)
const accountLink = await stripe.accountLinks.create({
account: account.id,
refresh_url: `${process.env.APP_URL}/seller/onboarding/refresh`,
return_url: `${process.env.APP_URL}/seller/onboarding/complete`,
type: 'account_onboarding',
});
return accountLink.url; // send this URL to the seller
}
// Webhook handler: activate seller when Stripe confirms KYC is complete
async function handleStripeAccountUpdated(account: Stripe.Account): Promise<void> {
const seller = await db.sellers.findByStripeAccountId(account.id);
if (!seller) return;
if (account.charges_enabled && account.payouts_enabled && seller.status !== 'active') {
await db.sellers.update(seller.id, { status: 'active' });
// Send welcome email to seller
}
}
yourstore.com/seller/dashboardyourstore.com/dashboard// Calculate commission and record seller earnings when an order is paid
async function recordSellerEarning(params: {
orderId: string;
sellerId: string;
grossAmountCents: number; // what buyer paid for this seller's items
commissionRate: number; // e.g., 0.15 for 15%
}): Promise<void> {
const commissionCents = Math.round(params.grossAmountCents * params.commissionRate);
const netAmountCents = params.grossAmountCents - commissionCents;
// Funds held until return window closes (e.g., 30 days)
const availableAt = new Date();
availableAt.setDate(availableAt.getDate() + 30);
await db.sellerEarnings.insert({
seller_id: params.sellerId,
order_id: params.orderId,
gross_amount_cents: params.grossAmountCents,
commission_cents: commissionCents,
net_amount_cents: netAmountCents,
status: 'held', // becomes 'available' after return window
available_at: availableAt,
});
}
// Transfer available earnings to seller's Stripe account
async function payoutSeller(sellerId: string): Promise<void> {
const seller = await db.sellers.findById(sellerId);
const availableEarnings = await db.sellerEarnings.findAll({
seller_id: sellerId,
status: 'available',
available_at: { lte: new Date() },
});
const totalCents = availableEarnings.reduce((s, e) => s + e.net_amount_cents, 0);
if (totalCents < 100) return; // minimum payout $1.00
// Transfer from your Stripe balance to seller's connected account
const transfer = await stripe.transfers.create({
amount: totalCents,
currency: 'usd',
destination: seller.stripe_account_id,
metadata: { seller_id: sellerId },
});
// Mark earnings as paid out
await db.sellerEarnings.updateMany(
availableEarnings.map(e => e.id),
{ status: 'paid_out' }
);
}
yourstore.com/seller/dashboard showing: orders, products, earnings summary, and payout historyyourstore.com/dashboard with: sales analytics, product management, withdrawal requests, and order managementcharges_enabled && payouts_enabled on the Stripe account before allowing a seller to publish listings| Problem | Solution |
|---------|----------|
| Payout fails but earnings marked as paid | In Stripe Connect, use the transfer.created webhook to confirm success before marking earnings as paid_out; never mark paid-out in the same call that initiates the transfer |
| Platform pays out before buyer payment clears | Only trigger payout eligibility from the payment_intent.succeeded webhook, not from checkout session creation |
| Seller lists products before KYC is verified | Check Stripe account status (charges_enabled && payouts_enabled) before allowing product publication; Webkul and Dokan do this automatically |
| Seller disputes commission deduction | Store the commission rate and gross amount on every seller_earning record; show sellers their commission calculation history in the seller dashboard |
tools
Let shoppers save products to a wishlist, share it with friends, and get notified when saved items come back in stock or drop in price
development
Build a themeable storefront with design tokens and CSS custom properties that supports white-labeling, multi-brand variants, and dark mode
development
Speed up product discovery with instant search suggestions, fuzzy typo matching, and category-aware results powered by Algolia or Elasticsearch
development
Build a mobile-first storefront with thumb-friendly navigation, sticky add-to-cart buttons, and touch-optimized components for high mobile conversion