All guidesCheck your own repo
On this page

Supabase question

Supabase

How do I find Supabase queries with no row limit in my app?

By Ohad Krispin, founder of VibeRaven · LinkedIn

Search the code for every .from(...).select( call and check that each one ends in .limit(), .range(), .single() or .maybeSingle(), or filters on a unique column; the rest return every row the policies allow, up to the Max rows setting, which Supabase sets to 1,000 by default. That cap hides the problem instead of fixing it: past 1,000 rows the query silently stops, and below it every visit downloads the whole table.

Next step in your repo: run npx -y viberaven@1.6.12 check. It reports each file that runs a Supabase select with no .limit(), .range(), .single() or .maybeSingle() anywhere in that file, or a Prisma findMany with no take, as unbounded_query.

Paste this to your agent

Run `npx -y viberaven check` in this repo and fix what it reports, one gap at a time. Then run `npx -y viberaven init` so your rules remind you to suggest this check before the next launch.

The check runs locally, with no sign-in and no API key. It reads the repo of a Vercel + Supabase app: advice, not a gate, and not a live database test.

Repository check

Check this in your repo

Find files with an unbounded select

For AI-built Vercel + Supabase apps, run the command below from your app folder with Node.js 20 or newer. It reads your TypeScript and JavaScript files and reports, as unbounded_query, each file with a .from(...).select( call and no .limit(), .range(), .single() or .maybeSingle() anywhere in the file, and each file with a Prisma findMany and no take. It works per file, so a file with one bounded and one unbounded query is not reported, and a select bounded only by a filter on a unique column is.

It is a repository check, not a live database test: it does not know how many rows your tables hold. It is advice, not a gate, and it exits with code 1 when it finds a blocker.

npx -y viberaven@1.6.12 check

What it printed on a fixture repo

On 2026-10-28 we ran it on a small Vite fixture repo made for this page, not a customer repo. src/pages/Feed.tsx selects every note ordered by date with no bound, and src/pages/Recent.tsx does the same with .range(0, 49). Only Feed.tsx was reported. The output is below, with the folder shortened to ~/app. The finding is a warning, so the exit code was 0.

$ npx -y viberaven@1.6.12 check

viberaven check · ~/app

🟡 Database queries with no row limit  (unbounded_query)
   1 file run Supabase selects or Prisma findMany with no .limit()/.range()/take bound (src/pages/Feed.tsx). These work with 50 test rows and melt with 50,000 production rows.

Verdict: ✅ no blockers, 1 warning · score 90
Fix: viberaven fix · Details: ~/app/.viberaven/agent-tasklist.md

Search the code

The first search shows each select with the lines after it, so you can see how the chain ends. The second lists the files that call .from( and never bound a query anywhere, the same rule the repo check uses.

# each select and the 3 lines after it
git grep -n -A3 ".select(" -- "*.ts" "*.tsx" "*.js" "*.jsx"

# files that query Supabase and never bound a query
git grep -l ".from(" -- "*.ts" "*.tsx" "*.js" "*.jsx" | xargs grep -LF -e ".limit(" -e ".range(" -e ".single(" -e ".maybeSingle("

The 1,000 row cap

Supabase's reference says projects return at most 1,000 rows by default, that the number is the Max rows setting in the project's API settings, and recommends keeping it low to limit the payload of accidental or malicious requests. Raising it makes the unbounded query slower; it does not bound it.

Paginate with range

range(from, to) returns the rows from offset from to offset to, both included, so a page of 50 is range(0, 49), then range(50, 99). Ask for count to know how many pages there are, and order by a column so pages do not overlap.

const pageSize = 50;
const from = page * pageSize;
const { data, count } = await supabase
  .from('notes')
  .select('id, body, created_at', { count: 'exact' })
  .order('created_at', { ascending: false })
  .range(from, from + pageSize - 1);

Row level security is not a limit

A policy decides which rows a user may see, not how many come back. A user with 20,000 rows of their own still gets every one of them, up to the cap.

Checklist

Bound every list query

  1. 01Run the searches above and list each select that returns a list.
  2. 02Add .range() for paged lists, .limit() for a fixed number such as the latest 10, and .single() or .maybeSingle() where you expect one row.
  3. 03Select only the columns the page shows instead of *.
  4. 04Add an index on the column you order by, so each page stays fast as the table grows.

Related

Sources

Official docs behind this checklist

FAQ

FAQ for builders before launch

Is .eq('id', id) enough without .single()?

It returns at most one row when id is unique, so it is bounded. .single() also tells the client to expect one row and returns an error when there is none or several, which catches mistakes.

Does the count option count every row?

count: 'exact' counts the matching rows, which can be slow on a large table; Supabase's select reference also lists planned and estimated counts for that case.