Skip to main content
RapidDev - Software Development Agency
weweb-tutorial

WeWeb Marketplace: Build a Multi-Vendor Platform with Stripe Connect

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.

What you'll learn

  • How to design a multi-vendor database schema in Supabase with RLS policies for buyers, sellers, and admins
  • How to build role-based views in WeWeb so buyers, sellers, and admins see different pages and UI
  • How to implement Stripe Connect for vendor onboarding and split payments
  • How to build a product listing page with search, filtering, and a shopping cart using WeWeb variables
  • How to structure the vendor dashboard for managing listings, orders, and payout status
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Advanced12 min read3-5 hours (across multiple sessions)WeWeb Essential+ (for code export/self-hosting if needed); Supabase Pro recommended for production; Stripe Connect requiredMarch 2026RapidDev Engineering Team
TL;DR

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

1

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.

typescript
1-- Injection point: Supabase Dashboard SQL Editor
2-- Core marketplace schema
3
4CREATE 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);
14
15CREATE 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);
26
27ALTER TABLE public.vendors ENABLE ROW LEVEL SECURITY;
28ALTER TABLE public.products ENABLE ROW LEVEL SECURITY;
29
30-- Vendors manage own profile
31CREATE POLICY "Vendors manage own profile"
32ON public.vendors FOR ALL
33USING ((SELECT auth.uid()) = user_id);
34
35-- All authenticated users can view vendor profiles
36CREATE POLICY "Public vendor profiles"
37ON public.vendors FOR SELECT
38USING (TRUE);
39
40-- Products: public read for active, vendor write for own
41CREATE POLICY "Public can view active products"
42ON public.products FOR SELECT
43USING (status = 'active');
44
45CREATE POLICY "Vendors manage own products"
46ON public.products FOR ALL
47USING ((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.

2

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.

3

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.

4

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.

typescript
1// Injection point: Supabase Edge Function → supabase/functions/create-checkout-session/index.ts
2import Stripe from 'https://esm.sh/stripe@14';
3import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
4
5const 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);
10
11Deno.serve(async (req) => {
12 const { items, buyerId } = await req.json();
13 const origin = req.headers.get('origin') || 'https://yourapp.com';
14
15 // Group items by vendor for split payments
16 const vendorGroups = items.reduce((groups, item) => {
17 if (!groups[item.vendorId]) groups[item.vendorId] = [];
18 groups[item.vendorId].push(item);
19 return groups;
20 }, {});
21
22 // 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.quantity
30 }));
31
32 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 });
39
40 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.

5

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.

6

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.

7

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

stripe-connect-onboarding.ts
1// Injection point: Supabase Dashboard → Edge Functions → New Function
2// Function name: create-stripe-connect-account
3// Called from WeWeb vendor registration form submit workflow
4
5import Stripe from 'https://esm.sh/stripe@14';
6import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
7
8const stripe = new Stripe(Deno.env.get('STRIPE_SECRET_KEY')!, {
9 apiVersion: '2023-10-16',
10 httpClient: Stripe.createFetchHttpClient()
11});
12
13const supabase = createClient(
14 Deno.env.get('SUPABASE_URL')!,
15 Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
16);
17
18Deno.serve(async (req) => {
19 const { vendor_id, email, return_url, refresh_url } = await req.json();
20
21 // Create Stripe Express account
22 const account = await stripe.accounts.create({
23 type: 'express',
24 email,
25 capabilities: {
26 card_payments: { requested: true },
27 transfers: { requested: true }
28 }
29 });
30
31 // Save Stripe account ID to vendor record
32 await supabase
33 .from('vendors')
34 .update({ stripe_account_id: account.id })
35 .eq('id', vendor_id);
36
37 // Create onboarding link
38 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 });
44
45 return new Response(JSON.stringify({
46 onboarding_url: accountLink.url,
47 stripe_account_id: account.id
48 }), {
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.

ChatGPT Prompt

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.

WeWeb Prompt

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

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.