Building a marketplace in WeWeb requires a multi-vendor data model in Supabase, role-based access for buyers and sellers, Stripe Connect for vendor payouts, and separate dashboards per role. WeWeb handles the frontend layer — Supabase manages the database, RLS policies, and Edge Functions for payment processing. Plan 2-4 weeks for a full MVP.
Build a Multi-Vendor Marketplace with WeWeb, Supabase, and Stripe Connect
A marketplace has three types of actors: buyers, sellers (vendors), and admins. Each needs a different UI and different data access. WeWeb handles all three views as a single-page application with role-based conditional rendering and page access control. Supabase provides the database with RLS policies that enforce data isolation between vendors. Stripe Connect handles the complex problem of splitting payments — collecting from buyers and routing funds to individual vendors minus a platform commission. This tutorial walks through the complete architecture: database schema design, role-based WeWeb setup, product listings with search, a cart and checkout flow, vendor onboarding with Stripe Connect, and the admin commission management view.
Prerequisites
- A WeWeb project with Supabase and Stripe plugins installed and configured
- A Stripe account with Connect enabled (requires business verification in Stripe Dashboard)
- Understanding of WeWeb user roles and the User Groups system
- Supabase Pro plan recommended for production (RLS, Edge Functions, higher connection limits)
- Basic familiarity with SQL for writing RLS policies
Step-by-step guide
Design the Multi-Vendor Database Schema
Design the Multi-Vendor Database Schema
All database work happens in the Supabase Dashboard SQL Editor, not in WeWeb. Your marketplace needs these core tables: vendors (id, user_id references auth.users, business_name, description, stripe_account_id, onboarding_complete, commission_rate), products (id, vendor_id, title, description, price, inventory_count, images array, category, status), orders (id, buyer_id, total_amount, platform_fee, stripe_payment_intent_id, status), order_items (id, order_id, product_id, vendor_id, quantity, unit_price, vendor_payout_amount). Enable RLS on all tables. The core security principle: vendors can only read and write their own products and see only their own orders. Buyers can read all active products but only their own orders. Admins (using a service role key via Edge Functions) can read everything.
1-- Injection point: Supabase Dashboard → SQL Editor2-- Core marketplace schema34CREATE TABLE public.vendors (5 id UUID PRIMARY KEY DEFAULT gen_random_uuid(),6 user_id UUID REFERENCES auth.users(id) UNIQUE NOT NULL,7 business_name TEXT NOT NULL,8 description TEXT,9 stripe_account_id TEXT,10 onboarding_complete BOOLEAN DEFAULT FALSE,11 commission_rate NUMERIC(4,2) DEFAULT 10.00,12 created_at TIMESTAMPTZ DEFAULT NOW()13);1415CREATE TABLE public.products (16 id UUID PRIMARY KEY DEFAULT gen_random_uuid(),17 vendor_id UUID REFERENCES public.vendors(id) NOT NULL,18 title TEXT NOT NULL,19 description TEXT,20 price NUMERIC(10,2) NOT NULL,21 inventory_count INTEGER DEFAULT 0,22 category TEXT,23 status TEXT DEFAULT 'active' CHECK (status IN ('active', 'inactive', 'draft')),24 created_at TIMESTAMPTZ DEFAULT NOW()25);2627ALTER TABLE public.vendors ENABLE ROW LEVEL SECURITY;28ALTER TABLE public.products ENABLE ROW LEVEL SECURITY;2930-- Vendors manage own profile31CREATE POLICY "Vendors manage own profile"32ON public.vendors FOR ALL33USING ((SELECT auth.uid()) = user_id);3435-- All authenticated users can view vendor profiles36CREATE POLICY "Public vendor profiles"37ON public.vendors FOR SELECT38USING (TRUE);3940-- Products: public read for active, vendor write for own41CREATE POLICY "Public can view active products"42ON public.products FOR SELECT43USING (status = 'active');4445CREATE POLICY "Vendors manage own products"46ON public.products FOR ALL47USING ((SELECT auth.uid()) = (SELECT user_id FROM vendors WHERE id = vendor_id));Expected result: Core marketplace tables created in Supabase with RLS policies. Seed with 2-3 test vendors and 10+ products.
Set Up Role-Based User Groups in WeWeb
Set Up Role-Based User Groups in WeWeb
In WeWeb, navigate to Plugins → Authentication → Supabase Auth → click Generate to create the roles and users_roles tables in Supabase. Create three roles in the Supabase Dashboard: go to the roles table (created by WeWeb's Generate button) and insert rows for 'buyer', 'seller', and 'admin'. In WeWeb, navigate to the User Groups panel (look for Users icon in left sidebar or in Plugins → Authentication settings). Create three User Groups: 'Buyers' (condition: user has role 'buyer'), 'Sellers' (condition: user has role 'seller'), 'Admins' (condition: user has role 'admin'). Now configure page access: for your Vendor Dashboard page, go to Page settings → Access control → Restrict to User Groups → select Sellers and Admins. For your Admin panel page, restrict to Admins only. The buyer-facing marketplace pages (product listing, cart) can remain public or require login.
Expected result: Three User Groups configured. Vendor Dashboard page access-controlled to Sellers only. Admin page restricted to Admins.
Build the Product Listing Page
Build the Product Listing Page
Create a new page named 'Marketplace' in WeWeb. In the Data panel, create a collection named 'products' connected to your Supabase products table with filter status = 'active'. Create page-level variables: 'searchQuery' (Text), 'selectedCategory' (Text), 'cartItems' (Array, app-level so it persists across pages). Add a search Input element at the top of the page — bind its On input workflow to change the 'searchQuery' variable and trigger a collection refetch with the search filter applied. Add a Select element for category filtering. Below, add a Container set to 'Repeat items' bound to collections['products'].data. Design the product card template inside this container: product image (bound to item.images[0]), title (item.title), price ('$' + item.price), and an 'Add to Cart' button. The Add to Cart button's On click workflow: Change variable value → append to cartItems array → push { productId: item.id, title: item.title, price: item.price, quantity: 1, vendorId: item.vendor_id }.
Expected result: Marketplace page displays a grid of active products with search and category filtering. Products can be added to a cart variable.
Implement the Cart and Checkout Flow
Implement the Cart and Checkout Flow
Create a Cart page. Bind a repeating container to the cartItems app variable. Add quantity increment/decrement buttons with On click workflows that use Change variable value → update the specific item's quantity in the cartItems array (use a map formula to update the matching item). Add a Remove button that filters the item out of cartItems. Show order total using a formula: reduce(cartItems, (sum, item) => sum + (item.price * item.quantity), 0). The Checkout button triggers a Supabase Edge Function (rather than direct Stripe from WeWeb) because Stripe Connect checkout requires server-side session creation. In the On click workflow: Supabase → Invoke Edge Function → 'create-checkout-session' with body: { items: cartItems, buyerId: user.id }. The Edge Function creates a Stripe Checkout Session with line items and Stripe Connect transfer data, then returns the session URL. Follow with a Navigate action using the returned URL to redirect to Stripe.
1// Injection point: Supabase Edge Function → supabase/functions/create-checkout-session/index.ts2import Stripe from 'https://esm.sh/stripe@14';3import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';45const stripe = new Stripe(Deno.env.get('STRIPE_SECRET_KEY')!);6const supabase = createClient(7 Deno.env.get('SUPABASE_URL')!,8 Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!9);1011Deno.serve(async (req) => {12 const { items, buyerId } = await req.json();13 const origin = req.headers.get('origin') || 'https://yourapp.com';1415 // Group items by vendor for split payments16 const vendorGroups = items.reduce((groups, item) => {17 if (!groups[item.vendorId]) groups[item.vendorId] = [];18 groups[item.vendorId].push(item);19 return groups;20 }, {});2122 // For simplicity: process first vendor (multi-vendor checkout needs Stripe Transfer Groups)23 const lineItems = items.map(item => ({24 price_data: {25 currency: 'usd',26 product_data: { name: item.title },27 unit_amount: Math.round(item.price * 100)28 },29 quantity: item.quantity30 }));3132 const session = await stripe.checkout.sessions.create({33 mode: 'payment',34 line_items: lineItems,35 success_url: `${origin}/order-success?session_id={CHECKOUT_SESSION_ID}`,36 cancel_url: `${origin}/cart`,37 metadata: { buyer_id: buyerId, items: JSON.stringify(items) }38 });3940 return new Response(JSON.stringify({ url: session.url }), {41 headers: { 'Content-Type': 'application/json' }42 });43});Expected result: Cart page shows items with quantity controls. Checkout button redirects to Stripe-hosted payment page.
Build Vendor Onboarding with Stripe Connect
Build Vendor Onboarding with Stripe Connect
Create a 'Vendor Registration' page restricted to logged-in users. The registration form collects business_name and description. On submit workflow: Action 1 → Supabase Database Insert → vendors table → insert row with user_id and business details. Action 2 → Invoke Supabase Edge Function → 'create-stripe-connect-account' with the user's email. This Edge Function calls stripe.accounts.create({ type: 'express', email }) and returns an account link URL for Stripe's hosted onboarding. Action 3 → Navigate to the returned Stripe Connect onboarding URL. When vendors complete Stripe onboarding, Stripe redirects to your return_url — set this to a WeWeb page named 'vendor-onboarding-complete'. On that page's On page load workflow, invoke another Edge Function that retrieves the account status and updates the vendor's onboarding_complete field and stripe_account_id in Supabase.
Expected result: Vendors can register, are redirected to Stripe's hosted KYC onboarding, and their stripe_account_id is saved to Supabase upon completion.
Build the Vendor Dashboard
Build the Vendor Dashboard
Create a 'Vendor Dashboard' page with access restricted to the Sellers User Group. In the Data panel, create a 'myProducts' collection: Supabase → products table → filter vendor_id equals the current user's vendor ID. To get the vendor ID: create a 'myVendor' collection pointing to the vendors table filtered by user_id equals the authenticated user's ID. Use myVendor.data[0].id as the vendor_id filter for myProducts. Add a DataGrid element bound to myProducts.data showing all vendor's products. Add an 'Add Product' button that opens a popup with a product creation form. The form's submit workflow calls Supabase Database Insert → products table → include vendor_id from myVendor.data[0].id. Add an orders DataGrid showing only order_items where vendor_id matches — create an 'myOrders' Supabase collection with the vendor_id filter applied.
Expected result: Vendor Dashboard shows the seller's own products and orders only. Vendors can add, edit, and deactivate their listings.
Add Admin Commission Management View
Add Admin Commission Management View
Create an 'Admin' page restricted to the Admins User Group. In the Data panel, create collections: 'allOrders' (Supabase orders table, no filter — admin sees everything), 'allVendors' (vendors table, no filter), 'platformRevenue' (Supabase view or RPC that sums platform fees). Add a DataGrid showing all orders with vendor name, order amount, platform fee, and payout status. Add a 'Mark Paid' button in each row's workflow that updates the order's payout_status to 'paid'. Use a formula to compute total platform revenue: reduce(collections['allOrders'].data, (sum, o) => sum + o.platform_fee, 0). Display this as a KPI card at the top. Security note: admin collections rely on Supabase RLS policies using the service role. Add an Edge Function for any bulk admin operations rather than calling Supabase directly from WeWeb with the anon key — admin operations should use the service role key which must never be exposed client-side.
Expected result: Admin page shows all orders, vendor list, and platform revenue. Admins can manage vendor status and mark payouts.
Complete working example
1// Injection point: Supabase Dashboard → Edge Functions → New Function2// Function name: create-stripe-connect-account3// Called from WeWeb vendor registration form submit workflow45import Stripe from 'https://esm.sh/stripe@14';6import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';78const stripe = new Stripe(Deno.env.get('STRIPE_SECRET_KEY')!, {9 apiVersion: '2023-10-16',10 httpClient: Stripe.createFetchHttpClient()11});1213const supabase = createClient(14 Deno.env.get('SUPABASE_URL')!,15 Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!16);1718Deno.serve(async (req) => {19 const { vendor_id, email, return_url, refresh_url } = await req.json();2021 // Create Stripe Express account22 const account = await stripe.accounts.create({23 type: 'express',24 email,25 capabilities: {26 card_payments: { requested: true },27 transfers: { requested: true }28 }29 });3031 // Save Stripe account ID to vendor record32 await supabase33 .from('vendors')34 .update({ stripe_account_id: account.id })35 .eq('id', vendor_id);3637 // Create onboarding link38 const accountLink = await stripe.accountLinks.create({39 account: account.id,40 refresh_url: refresh_url || 'https://yourapp.com/vendor/onboarding',41 return_url: return_url || 'https://yourapp.com/vendor/onboarding-complete',42 type: 'account_onboarding'43 });4445 return new Response(JSON.stringify({46 onboarding_url: accountLink.url,47 stripe_account_id: account.id48 }), {49 status: 200,50 headers: { 'Content-Type': 'application/json' }51 });52});Common mistakes
Why it's a problem: Attempting to transfer Stripe payouts to vendors directly from WeWeb frontend code
How to avoid: Stripe Connect transfers require your platform's secret key, which must NEVER be exposed in the frontend. All Stripe Connect operations (creating accounts, processing transfers, creating checkout sessions with transfer_data) must go through Supabase Edge Functions using the secret key stored as an Edge Function secret.
Why it's a problem: Using WeWeb's User Groups page access restriction on the Launch hosting plan (below Scale tier)
How to avoid: Role-based page access in WeWeb requires the Scale hosting plan. On lower plans, implement access control via On page load workflows: if the user lacks the seller role, redirect immediately to the homepage using the Navigate action. This is frontend-only enforcement — always back it up with Supabase RLS policies.
Why it's a problem: Creating a single 'allProducts' collection that fetches every product for both buyers and vendors
How to avoid: Create separate collections for different roles: 'products' (public, filtered to status='active') for buyers, 'myProducts' (filtered to vendor_id = current vendor) for the vendor dashboard. This avoids loading data the user should not see and keeps collection sizes small.
Why it's a problem: Forgetting to handle Stripe's webhook for checkout.session.completed
How to avoid: Payment success must be confirmed server-side via a Stripe webhook, not solely from the success_url redirect (which can be manually triggered). Set up a Supabase Edge Function as a Stripe webhook endpoint that listens for checkout.session.completed and creates the order record and order_items in Supabase when payment is confirmed.
Best practices
- Never store Stripe secret keys or service role keys in WeWeb variables or REST API plugin headers — always route sensitive operations through Supabase Edge Functions
- Design your RLS policies before building any WeWeb UI — incorrect policies cause silent data leaks that are hard to debug once the frontend is built
- Use Supabase database functions (RPC) for complex operations like creating an order with multiple order_items atomically in a single transaction
- Give vendors a clear onboarding checklist in their dashboard showing completion status: profile complete, Stripe account connected, first product listed
- Implement idempotency keys in your Stripe API calls to prevent duplicate charges if the user submits the checkout form twice
- Use WeWeb's Collections filter 'Ignore if empty' to build reusable filtered collections that work with or without filter values applied
- Test your marketplace with at least two Stripe test accounts (platform + vendor) before going live to verify that payouts route correctly
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I'm building a multi-vendor marketplace with WeWeb frontend and Supabase backend. I need a PostgreSQL function that creates an order atomically: takes a buyer_id, cart_items array, and platform_fee_percentage, inserts an order record and all order_items in a single transaction, and returns the created order ID. Write this as a Supabase RPC function.
In my WeWeb marketplace, I have a 'cartItems' app-level variable (array) and a products collection. Help me write a workflow that: checks if a product is already in the cart (by product ID), increments quantity if it exists, or appends a new item if it doesn't. Show me the formula for the Change variable value action.
Frequently asked questions
Can WeWeb handle the shopping cart state across page navigation?
Yes. Create the cartItems variable as an App-level variable (Data panel → Variables → New → Array → Scope: App). App-level variables persist across page navigation within the same browser session. For persistence across sessions (cart survives page close), save cartItems to localStorage via a Custom JavaScript action whenever the cart changes, and restore from localStorage On app load.
Does Stripe Connect work with WeWeb's native Stripe plugin?
WeWeb's Stripe plugin supports standard Checkout and Payment Intents for direct payments. Stripe Connect (for multi-vendor payouts) requires creating checkout sessions with transfer_data and application_fee_amount parameters, which must be done server-side. Use Supabase Edge Functions to create Connect checkout sessions and trigger payouts — these call the Stripe API with your secret key server-side, then return the checkout URL to WeWeb.
How many vendors can a WeWeb marketplace support before performance degrades?
WeWeb marketplace performance depends almost entirely on your Supabase query performance, not on WeWeb itself. With proper indexes on vendor_id, status, and category columns, and server-side pagination via LIMIT/OFFSET in your collection queries, a marketplace can handle thousands of vendors and tens of thousands of products. Implement search via Supabase's full-text search (tsvector) or integrate Algolia for sub-second search on large catalogs.
Can a user be both a buyer and a seller on the same marketplace?
Yes. Assign both 'buyer' and 'seller' roles to the user in Supabase's users_roles table. In WeWeb, user groups use AND logic (user must have ALL roles in the group), so create a 'Buyers' group requiring only the 'buyer' role and a 'Sellers' group requiring only the 'seller' role. A user with both roles will match both groups and have access to both buyer and seller pages.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation