Return inserted row ID in Supabase JS
If `data` is null after an insert, Supabase is doing exactly what the current docs say. You need to ask for the row back explicitly.
Return inserted row ID in Supabase JS#
The symptom is usually this:
const { data, error } = await supabase
.from('projects')
.insert({ name: 'Alpha' })
console.log(data) // null
The root cause is not your table and not your primary key. The current Supabase docs say .insert(), .update(), .upsert(), and .delete() do not return modified rows by default.
If you want the inserted ID back, you must request it.
The exact fix#
const { data, error } = await supabase
.from('projects')
.insert({ name: 'Alpha' })
.select('id')
.single()
if (error) throw error
console.log(data.id)
That is the whole move:
.insert(...)writes the row.select('id')asks PostgREST to return the inserted row.single()unwraps the single-row array into one object
If you want more than just the ID#
Ask for the other columns explicitly:
const { data, error } = await supabase
.from('projects')
.insert({ name: 'Alpha' })
.select('id, name, created_at')
.single()
if (error) throw error
This is usually cleaner than doing a second query immediately after the insert.
If you are inserting multiple rows#
Do not use .single() then.
const { data, error } = await supabase
.from('projects')
.insert([
{ name: 'Alpha' },
{ name: 'Beta' },
])
.select('id, name')
if (error) throw error
console.log(data)
Now data is an array, which is what you want.
The common mistake#
People often write an insert, see data === null, and then assume they need a trigger, RPC, or a manual max(id) query. None of that is necessary for the ordinary case. The client just needs .select().
The small but important production rule#
Ask only for the columns you need:
.select('id')
That keeps payloads smaller and makes the call's intent obvious in review.
Where this matters most#
This comes up constantly in:
- create-and-redirect flows
- dashboard actions that need the new record ID
- nested UI state where you want the inserted row immediately
- server actions that create a record and then navigate to it
If you are building those flows next, these pair well:
- How to insert into multiple tables with one Supabase API call 2026
- Next.js Server Actions with Supabase: Complete Guide
- Next.js Supabase Type Safety Guide
- Debugging Supabase RLS Issues: A Step-by-Step Guide
References#
Frequently Asked Questions
One email a month — no fluff
RLS gotchas, Next.js cache debugging, and the one Supabase setting that bit me last month.
Continue Reading
How to get COUNT(*) in Supabase
If you are fetching whole result sets just to count them, you are paying for bandwidth and latency you do not need. Supabase already returns counts in query metadata.
Create an enum column in Supabase – 2026 guide
Step‑by‑step guide to adding a PostgreSQL enum type and column in Supabase, including verification and common pitfalls.
Supabase client permission denied for schema public – fix
Learn the exact steps to grant the right permissions in Supabase and stop the 'permission denied for schema public' error from breaking your app.
Browse by Topic
Find stories that matter to you.
