Browse Templates/Multi-Vendor Marketplace

Multi-Vendor Marketplace

A production-ready multi-vendor marketplace database schema inspired by platforms like Etsy, Amazon Marketplace, and Shopify. Includes user accounts, vendor store management, product listings with per-store pricing, order processing, customer reviews with ratings, and automated payout tracking for sellers. Designed to handle multiple independent sellers operating under a single marketplace umbrella.

PostgreSQL11 tablesE-Commerce
marketplacemulti-vendore-commercepayoutsreviewsstores
Created with ERD Studio

About this schema

An order belongs to one store, not to a basket. orders carries both buyer_id and store_id, so a cart spanning three vendors becomes three orders — which is the only arrangement where each seller ships, is paid and is disputed independently. The shared checkout is an application concern; the database records what each store actually owes and is owed.

There are two kinds of review and they answer different questions. store_reviews rates the vendor, while reviews rates a product and points at an order_item. That third key is what makes a review verifiable: it can only be written by someone whose order actually contained the thing being reviewed.

Money owed to sellers is its own table rather than a sum over orders. payouts stores the amount, the commission taken, a period and a status, because settlement runs on its own clock — a payout covers a date range regardless of which orders fall inside it, and it can be pending, paid or failed independently of whether the orders were delivered.

Key relationships

users → stores → products
One users table for both sides, with role telling them apart. A store names its owner, so a seller is a buyer who also has a storefront.
orders → stores / users (buyer_id)
Each order is one buyer against one store. A multi-vendor cart is several orders sharing nothing but a checkout moment.
reviews → order_items
The link that makes a review verified. Without a matching order line there is no purchase to back the rating up.
stores → payouts
Settlement per store per period, with commission_amount recorded beside the gross so the split is auditable after the fact.
disputes → orders / users (opened_by)
A dispute names the order and who raised it, since either side can open one — and resolution is stored, not just the outcome flag.
categories → categories (parent_id)
Self-referencing tree so the catalogue can be deepened without a schema change, with sort_order controlling display.

Design decisions

One order per store rather than one order per checkout

Splitting the cart at checkout feels like extra work and removes an entire class of problems. A single order spanning vendors would need per-store status, per-store shipping and per-store disputes inside one row, and a refund from one seller would have to partially cancel a shared total. Separate orders make each of those the normal path. The price is that the buyer's single payment has no row of its own here.

commission_rate on the store, commission_amount on the payout

The rate is current policy and can be renegotiated; the amount is what was actually withheld for a settled period. Keeping only the rate would make every historical payout recalculate against today's terms, which is exactly the number a seller will dispute. Storing both means a rate change applies going forward and never rewrites what was already paid.

order_items stores product_name alongside product_id

Sellers edit and delete listings constantly on a marketplace. The copied name keeps an order readable when the product is gone, while the id keeps the link for reviews and reordering. The duplication is deliberate: one column is for display and is frozen, the other is for identity and is live.

Buyers and sellers share one users table

A role column instead of two tables, because the same person sells and buys, and splitting them would duplicate every account and make a seller reviewing another store impossible to represent. The cost is that seller-only fields have nowhere natural to live — they end up on stores, which is why the storefront, not the user, carries the commission rate and the activation flag.

Tables in this template

usersUsers
8 cols
storesStores
10 cols
store_reviewsStore Reviews
6 cols
categoriesCategories
6 cols
productsProducts
10 cols
product_imagesProduct Images
6 cols
ordersOrders
9 cols
order_itemsOrder Items
7 cols
reviewsReviews
8 cols
payoutsPayouts
10 cols
disputesDisputes
9 cols

Frequently asked

How does a cart with items from three stores work?
It becomes three orders, one per store, created in the same checkout. Each then ships, settles and can be disputed on its own, which is what sellers need — they have no stake in each other's fulfilment.
Why does reviews point at order_item_id?
So a review can be proven to come from a buyer who received the product. It also pins the review to one purchase, which is what stops the same order being used to post repeated ratings.
Can I calculate payouts from orders instead of storing them?
Only until the first rate change, refund or dispute. The payout row records what was actually transferred for a period, including the commission withheld — recomputing it later gives a number that no longer matches the bank transfer.
Where is the buyer's payment recorded?
Not in this schema. orders carries total_amount and status, and payouts covers the seller side. A payment provider reference belongs on the order, and refunds would need their own table — both are the natural next additions.
Why are store reviews separate from product reviews?
They rate different things. A product can be good while the seller ships late, and a vendor rating has to survive the product being delisted — which it cannot if it hangs off a product row.