
Multi-Tenant Architecture for Marketplaces — Vendor Trust at Scale
RLS-enforced vendor isolation that survives buyer search, dispute access, and vendor offboarding — so suppliers do not bail the day a competitor signs up.
The problem
Read/write asymmetry on the same tables
Marketplace tenancy is harder than single-tenant SaaS tenancy because the data model has to do two opposite things on the same tables at the same time. Vendors must be isolated from each other — supplier A cannot see supplier B's orders, payouts, customer list, or pricing — because the moment they can, supplier A churns the week a competitor signs up. But buyers must read across every vendor's catalog, because that cross-vendor discovery is the entire point of a marketplace. The naive answer of "filter by vendor_id everywhere" collapses the first time a buyer hits the search endpoint and gets back zero results, or the first time a vendor hits an admin endpoint and gets back everyone's results.
Disputes, branding, offboarding, and noisy neighbors
The failure modes compound. Disputes need both parties to see the same thread, which means a hard vendor-isolation boundary has to soften in exactly one direction without collapsing entirely. Per-vendor branding and feature flags need to live somewhere — vendor A wants their logo and their checkout copy and only the payment methods they support — without forking the application or shipping a deploy per vendor. Vendor offboarding turns into a six-week scavenger hunt across services because no one mapped where vendor data actually lives, and the GDPR clock starts the moment the request comes in. And the day a vendor runs a flash sale and drives 50x normal traffic, every other vendor's checkout slows down because the infrastructure was sized for the median and one noisy neighbor consumed it.
Supplier trust running in reverse
The marketplace founder watching this happen knows the cost is not just engineering rework. It is supplier trust. Vendors who lose confidence that their data is genuinely isolated do not file support tickets — they quietly stop investing in the channel, stop uploading new SKUs, stop integrating their fulfillment, and within a quarter they are gone. The single moment a vendor sees another vendor's order data, by accident or by curiosity, is the moment the platform's network effect starts running in reverse.

What changes for your business
Per-command RLS policies for the buyer/vendor split
A correctly architected marketplace pushes the vendor boundary into Postgres itself and uses the fact that RLS supports separate policies per command and per role to handle the read/write asymmetry directly. The official Postgres documentation describes policies that apply to ALL commands or specifically to SELECT, INSERT, UPDATE, or DELETE — which is exactly what lets the orders table carry one set of policies for the vendor role (visible only when vendor_id matches) and a different (absent) set for the buyer role. The catalog table works the opposite way: a permissive SELECT policy returns rows to any authenticated buyer alongside a vendor-scoped SELECT policy that returns only the vendor's own rows. Because Postgres combines permissive policies with OR, the buyer sees the union of every vendor's catalog and the vendor sees only their own, on the same physical table, with no application code arbitrating the difference.
OR-then-AND dispute access
Disputes get the harder treatment. A disputes table carries a permissive policy for the originating buyer, a permissive policy for the assigned vendor, and an AS RESTRICTIVE policy that requires membership in the dispute_participants table for the row to be visible at all. The Postgres documentation states that "when a mix of permissive and restrictive policies are present, a record is only accessible if at least one of the permissive policies passes, in addition to all the restrictive policies" — which composes to "participant AND (buyer-of-record OR vendor-of-record)" at the database layer. The access semantics a dispute needs are enforced before any endpoint sees the row.
Vendor settings as rows, not branches
Per-vendor branding and feature flags live in a vendor_settings table with vendor-scoped RLS, read on every request, referenced by key in application code. Adding a new flag is a column addition and a settings write, not a deploy. The vendor onboarding flow writes the row; the offboarding flow archives and erases it alongside everything else under the same vendor_id.
One-key vendor offboarding under GDPR Article 17
Vendor offboarding is the operation most marketplaces under-invest in until the first request arrives. The schema is designed so a vendor's complete data footprint is reachable from one foreign key — vendor_id — across every tenant-scoped table. Export walks every such table and emits a structured archive; erase deletes rows in dependency order. GDPR Article 17 requires fulfilling an erasure request within one month, so the operation has to be a script someone can run, not a quarterly project to assemble. Logs, backups, and any third-party integration that received vendor data each need their own retention and redaction policy from day one — bolting them on after a request lands is where compliance audits fail.
Tiered rate limits for noisy-neighbor containment
The noisy-neighbor problem — one vendor's flash sale degrading every other vendor's checkout — gets handled with tiered rate limits at the API gateway and per-vendor queues for anything touching shared compute. AWS's serverless SaaS reference architecture describes this directly: tiered throttling at the gateway layer with usage plans, so that basic-tier tenants cannot starve premium-tier tenants of capacity. The cap is configuration, not per-vendor infrastructure, and the bound on blast radius is the difference between a vendor's traffic spike being a vendor problem and the same spike being a marketplace-wide incident.
For the marketplace business, what changes is vendor retention. Suppliers who can trust that their order book, their pricing, their customer list, and their margin data are structurally invisible to the vendor next to them — not "we promise we filter by vendor_id everywhere" but "the database returns zero rows if anyone forgets" — stop hedging. They upload more SKUs. They integrate fulfillment. They stop building a second presence on a competing platform. The architecture is not the product, but it is the foundation the product needs to compound on.

What gets shipped for marketplaces
A marketplace tenancy engagement leaves your codebase with the surface area that this combination needs, in addition to the baseline tenancy work in the parent service.
A buyer/vendor RLS split on every shared table — catalog and reviews carry permissive SELECT policies for buyers plus vendor-scoped policies for the vendor's own rows; orders, payouts, customer-of-vendor data, and pricing carry vendor-only policies with no buyer-visible read path. Every policy uses ALTER TABLE ... FORCE ROW LEVEL SECURITY so service roles do not silently bypass them. The split is documented in a tenancy map that lives in the repository, so the next engineer onto the codebase does not have to reverse-engineer which tables are read-shared and which are vendor-isolated.
A dispute table with the OR-then-AND policy stack described above, plus an admin role for your support team that can see disputes across vendors with separate audit logging on every access. The audit log is itself a tenant-scoped table — support actions are visible to the involved vendor and buyer but not to other vendors.
-- Buyers see every vendor's catalog; vendors see only their own
alter table public.catalog_items enable row level security;
alter table public.catalog_items force row level security;
create policy buyer_browse on public.catalog_items
for select to authenticated_buyer
using (true);
create policy vendor_own_catalog on public.catalog_items
for all to authenticated_vendor
using (
vendor_id = ((auth.jwt() -> 'app_metadata') ->> 'vendor_id')::uuid
) with check (
vendor_id = ((auth.jwt() -> 'app_metadata') ->> 'vendor_id')::uuid
);
create policy dispute_buyer_read on public.disputes
for select using (buyer_id = auth.uid());
create policy dispute_vendor_read on public.disputes
for select using (
vendor_id = ((auth.jwt() -> 'app_metadata') ->> 'vendor_id')::uuid
);
create policy dispute_participant_required on public.disputes
as restrictive for select
using (
exists (
select 1 from public.dispute_participants p
where p.dispute_id = disputes.id
and p.actor_id = auth.uid()
)
);
A vendor_settings table for branding (logo, color tokens, custom domain) and feature flags (enabled payment methods, reviews on/off, fulfillment integrations), with vendor-scoped RLS and an application-side helper that reads settings by key. The schema is small on purpose — flags are rows, not columns — so adding a new flag does not require a migration.
Vendor offboarding scripts: an export that walks every tenant-scoped table by vendor_id and emits a structured archive in a portable format, and an erase that deletes those rows in dependency order with foreign-key cascades verified up front. The scripts run against a single vendor_id and complete within the GDPR one-month window with margin. The logs and backups path is documented separately, with retention windows and the redaction path for any third-party integration the vendor's data reached.
Tiered rate limits at the API gateway with per-vendor usage plans, plus per-vendor queues for background work — image transcoding, search indexing, bulk SKU uploads, payout batches — sized so one vendor's spike cannot consume the worker pool the rest of the marketplace shares. The tiering structure is wired up to your pricing so the configuration matches the contract.
A tenancy audit script extended for the marketplace case — in addition to the parent service's checks (FORCE on every tenant table, immutable claims, no service-role drift), it verifies that every read-shared table has the buyer policy and every vendor-isolated table does not, and flags any new migration that adds a vendor-scoped table without explicit policy on the buyer access path.
What buyers ask first
Marketplace founders ask a tight set of questions before signing: "How do we let buyers search across vendors without leaking vendor data?", "What happens during a dispute?", "How does vendor offboarding actually work?", and "What stops one vendor's flash sale from breaking checkout for the rest of the marketplace?" The short answers: separate RLS policies for the read side and the write side on the same tables; a dedicated dispute table with the OR-then-AND policy stack; one foreign key reaches every row and the export-and-erase scripts run on demand; tiered rate limits at the gateway and per-vendor queues below it. The FAQ section above covers the longer versions.
Common failure modes
The marketplace tenancy bugs that show up most often cluster into a handful of patterns worth naming so your team recognizes them on sight.
The first is a vendor-scoped policy that accidentally restricts the buyer-search path. Someone adds RLS to a table thinking only vendors will read it, forgets buyers also need to, and the search endpoint silently returns zero results — which only surfaces when a buyer complains. The audit script catches this by checking that every catalog-side table has a buyer policy.
The second is dispute access that grants too much. A permissive dispute policy without the restrictive participant-membership check leaks every dispute to every buyer who happens to know an ID. The OR-then-AND stack is non-optional for that table.
The third is offboarding that misses logs and backups. Erase deletes the database rows, but old request logs still contain vendor names, an analytics warehouse still has a copy from last week's ETL, and a third-party fulfillment provider has the vendor's customer addresses sitting in their account. The retention and redaction policy has to ship with the architecture, not after the first request.
The fourth is a single vendor running an unintended denial-of-service against the rest of the marketplace — a bug in their integration that retries every 50ms, a flash sale no one was warned about, an admin import of a million SKUs at midnight. Without per-vendor rate limits and bounded-concurrency queues, the marketplace experiences this as platform-wide degradation. The cap belongs at the gateway and at the worker pool; either alone is insufficient.
Proof this pattern lands
BoostFrame Engineering AI runs the same multi-tenant stack across seven production applications today — RLS-enforced isolation, JWT auth with rotating refresh tokens, per-tenant Stripe billing — the foundation that has supported 200K+ AI-assisted keywords generated, 1,500+ AI scans run, and 7,000+ sites automated for paying customers across the suite. BFEAI is not a marketplace product, and we do not represent it as one. What transfers is the architecture: the RLS-as-tenant-boundary discipline, the FORCE-on-every-table CI check, the immutable-claim policy pattern, the per-tenant rate-limit story, and the offboarding-as-scripted-operation expectation. The marketplace-specific work — the buyer/vendor read-side split, the dispute policy stack, the vendor_settings model, and the per-vendor queue sizing — is the part we architect against your existing platform shape, not something we bring in pre-built. The author is Bill Fackelman, co-founder and CTO of BoostFrame Engineering AI.
Outcomes you should expect
What this delivers
- Vendors trust you with their data and do not bail when a competitor signs up — because the architecture makes 'vendor B saw vendor A's orders' structurally impossible, not just unlikely.
- Buyer search across every vendor's catalog still works at speed, because the read-side pattern uses a separate policy from the vendor-isolation policy instead of fighting it.
- A dispute thread is visible to exactly the two parties involved and your support team, with no engineer having to remember the access rules — restrictive RLS policies handle it at the database layer.
- Vendor offboarding is a one-command export plus a one-command erase, not a six-week scavenger hunt across logs and backups — because the schema was built knowing this day would come.
- One vendor's flash sale does not crater another vendor's checkout, because tiered rate limits at the API gateway cap blast radius before it reaches shared infrastructure.
Industry data
By the numbers
Postgres lets a single table carry separate policies per command — FOR SELECT, INSERT, UPDATE, or DELETE — which is what enables a marketplace to give a vendor write-access only to their own orders while letting buyers read across every vendor's catalog through a different policy on the same table.
When multiple Postgres RLS policies apply to a query, permissive policies are combined with OR and restrictive policies are combined with AND — so a buyer-search policy and a vendor-isolation policy can coexist on the same table, and an AS RESTRICTIVE dispute-access policy can layer on top to require dispute-party membership in addition to whichever role is querying.
Postgres RLS lets the same table use separate USING and WITH CHECK expressions, which means a marketplace can let a vendor see their pending payouts (USING) while preventing them from editing the payout amount (WITH CHECK) — a split the read/write boundary of a marketplace ledger depends on.
AWS's serverless SaaS guidance describes a pool model where 'all tenants share a common storage and compute infrastructure' and prescribes tiered throttling via API Gateway usage plans 'where basic tier tenants, for example, have throttling policies that limit their ability to impact the experience of higher tier tenants' — the mechanism a marketplace uses to keep one vendor's spike off another vendor's checkout.
GDPR Article 17 requires controllers to fulfill an erasure request within one month, and SaaS providers must be able to find, export, and delete a specific user's data across databases, logs, backups, and third-party services — which for a marketplace means an offboarding vendor's complete data footprint has to be locatable on a single query path, not scattered across services no one mapped.
Live in production today
The same engineering, shipped in production at BFEAI.
I'm co-founder & CTO of Be Found Everywhere (BFEAI), a 7-app AI SaaS platform running today. The work I deliver for clients is the work I do every week on my own platform.
7
Production apps
200K+
Keywords generated
1,500+
AI scans run
7,000+
Sites automated
Common questions
What buyers ask before reaching out
How do you let buyers search across every vendor's catalog while keeping vendors blind to each other's orders?
The read-side and the write-side use different RLS policies on the same tables. The catalog table has a permissive SELECT policy that returns rows for any authenticated buyer regardless of vendor_id, alongside a vendor-scoped SELECT policy that returns only the calling vendor's rows. Postgres combines permissive policies with OR, so a buyer sees the union (all catalogs) and a vendor sees only their own. The orders table works the opposite way — no buyer-wide SELECT policy exists, so a vendor only ever sees their own orders even though the data lives in a shared table. The split is enforced by the database, not by trusting every endpoint to remember the rule.
What happens during a dispute, when both vendor and buyer need to see the same order thread?
Disputes are the case where naive isolation breaks. The pattern is a separate disputes table with its own RLS policies — a permissive policy that lets the originating buyer read, a second permissive policy that lets the assigned vendor read, and an AS RESTRICTIVE policy that requires both parties to be members of the dispute_participants table for the row to be visible at all. Because permissive policies combine with OR and restrictive policies are AND'd on top, the net rule reads 'you must be a participant AND either the buyer-of-record or the vendor-of-record' — which is exactly the access semantics a dispute needs.
How do you handle per-vendor branding and feature flags without forking the codebase?
Vendor-scoped configuration lives in a vendor_settings table that the app reads on every request, with RLS scoped so each vendor only ever reads their own settings. Branding (logo, color tokens, custom domain) and feature flags (which payment methods are enabled, whether reviews are on, which fulfillment integrations are wired up) are all rows in that table. The app code references settings by key, not by vendor identity — so adding a new feature flag is a column addition and a settings write, not a deploy. Cross-vendor admin tools query the same table with a service role that bypasses the vendor scope, gated behind separate audit logging.
How do you offboard a vendor cleanly when they leave the platform?
The schema is designed so a vendor's complete data footprint is reachable from one foreign key — vendor_id — across every tenant-scoped table. Offboarding is two operations: an export that walks every table with a vendor_id column and emits a structured archive (JSON or CSV per table), and an erase that deletes those rows in dependency order. GDPR requires fulfilling an erasure request within a month, so the operation has to be runnable on demand, not a quarter-long project. The harder part is logs and backups, which need their own retention and redaction policy from day one — bolted on later, it is the part that fails compliance audits.
How do you stop one vendor's flash sale from crushing every other vendor's checkout?
Two layers. At the API gateway, per-tenant usage plans cap the requests-per-second any single vendor can drive — AWS's serverless SaaS reference architecture calls this 'tiering' and uses it precisely so that one tenant's spike cannot starve another tenant's experience. Below that, anything that touches shared compute (background jobs, search indexing, image transcoding) runs on per-vendor queues with bounded concurrency, so a vendor publishing 100K SKUs at once does not block the worker pool the rest of the marketplace shares. Both layers are configuration; neither requires per-vendor infrastructure.
Why not just give each vendor their own database?
Because the read-side of a marketplace is buyer search across every vendor's catalog, and a database-per-vendor turns every search query into a fan-out across N databases. The cost — both operational and latency — compounds with vendor count. The pattern that scales is shared infrastructure with RLS-enforced isolation on the write side and policy-controlled visibility on the read side, plus per-tenant rate limits to bound the noisy-neighbor risk. Database-per-vendor is the right answer when vendors are enterprise customers paying for genuine infrastructure isolation; it is the wrong answer when the business model depends on horizontal buyer discovery across the long tail of vendors.
What does an engagement typically cover and how long does it run?
A marketplace tenancy engagement runs the same 3–5 week shape as the parent service, scoped to the marketplace-specific surface area — vendor onboarding flow, buyer-vs-vendor RLS split on the core tables, dispute table with the OR-then-AND policy stack, vendor_settings for branding and flags, the offboarding export-and-erase scripts, and tiered rate limits at the gateway. Greenfield is shorter; a refactor of an existing marketplace with active vendors runs longer because the data migration has to happen without anyone losing access mid-flight.
Ready to see if this is a fit?
A 15-minute call. No deck, no slides. We talk about what you're shipping and where engineering is the bottleneck. Either way, you walk away with a senior engineer's read on your situation.