RowShield
Guides

Listings are public; everything around them is not

Directories and marketplaces publish on purpose: a listing exists to be read by strangers. That clarity makes the security model unusually teachable — the public surface is a status, not an accident, and everything adjacent to it (drafts, seller identities, contact threads, payout details) is private until promoted. On Supabase, that promotion is a policy clause.

Failures here have a flavour: tables built for the public feed that never grew fences for the private remainder. The listings table ships readable — fine; the sellers table ships readable — not fine; a drafts column rides along inside the public payload — quietly wrong. The pass below sorts intended publicity from inheritance.

RowShield does not detect this yet. This guide gives you the catalog queries to check it yourself. The nine rules that do ship are listed on the rules index.

Make publication a column, not a hope

Listings need an explicit status — draft, review, published, archived — and the public policy reads only the published arm. Anything else invites accidents: a half-edited listing appearing in the feed, or a suspended seller’s rows still serving. Status also powers the moderation workflow for free, which encourages teams to keep it truthful.

ALTER TABLE public.listings ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.listings FORCE ROW LEVEL SECURITY;

CREATE POLICY "public_reads_published_listings"
  ON public.listings
  FOR SELECT
  TO anon, authenticated
  USING (status = 'published');

CREATE POLICY "sellers_manage_own_listings"
  ON public.listings
  FOR ALL
  TO authenticated
  USING (seller_id = (SELECT auth.uid()))
  WITH CHECK (seller_id = (SELECT auth.uid()));

The tables around the listings

Note what the pair above does not do: it never shows a stranger a draft, and it never lets a seller touch another seller’s rows. Column-level nuance — hiding a pending price change from the public while showing it to its seller — arrives later via views; start with row-level truth and refine.

Sellers deserve their own pass. Public needs a display name and perhaps a joined date; payouts, tax identifiers, contact emails and performance notes belong to the seller and staff alone. Split the profile into a public projection and a private base early — retrofitting the split after scrapers have cached the fat version helps nobody.

Marketplace extras multiply quickly: saved searches, watchlists, offers, dispute threads. Each is participant-visible at most, each wants the standard ownership traversal, and each is a candidate for the forgotten-policy pile. Inventory them with the same four-label exercise as any social product: public, member, participant, staff.

Search, aggregates and side channels

Public does not mean unprotected against abuse. Feed endpoints revealing everything published are fine; the same openness on user-directed data — a seller’s complete sales ledger, say — is not. Distinguish browsing from mining: rate-aware design and narrowed projections keep the public square useful without handing competitors your whole ledger in one evening.

Aggregates tempt shortcuts. Computing view counts or rating summaries with client credentials against raw tables widens reads subtly; prefer materialised summaries refreshed server-side. The pattern keeps public numbers public while underlying events stay fenced — and spares you the day someone asks why competitor analytics were technically possible.

After the fences stand

Marketplace schemas evolve with features, and features arrive with tables. RowShield does not automate this checklist; the four-label walk stays a person’s task, repeated quarterly. What machinery adds is the between-walks noticing: continuous scanning catches the new offers table serving publicly because its migration copied an older one.

Set the watcher and move on: scheduled scans diff RLS posture and anonymous reachability, alerts fire on transitions rather than nagging on schedule, and reads touch catalogue and bucket metadata only — never listing bodies or seller records. RowShield is an independent product, unaffiliated with Supabase.

Frequently asked

Must anonymous visitors get their own policy arm?
Wherever content is genuinely public, yes. Naming anon explicitly in one narrow policy keeps the grant visible and reviewable, whereas achieving publicness by disabling RLS publishes drafts, archives and seller edits in the same breath.
How much of a seller profile should be public?
Whatever supports trust in a transaction: display name, joined date, review summary. Contact details, payout data and internal notes fail the test — they serve the platform’s operation, not the buyer’s decision. Publish a deliberate projection and let the private table hold the rest.
We scrape-proofed the interface. Are we done?
No — the interface was never the boundary. Anyone holding the project URL and anon key queries the REST surface directly, so what matters is what policies serve, not what the screen displays. Fence the tables and the question dissolves at the correct layer.

Check your project in about ten seconds

Paste a URL. No signup, no writes, nothing stored.

Run the free audit
marketplace listings rlsdirectory site supabase securitypublic listings private sellerssupabase listing table policies