Skip to main content
RapidDev - Software Development Agency
supabase-tutorial

How to Avoid Duplicated Entries in Supabase

Prevent duplicate entries in Supabase by adding UNIQUE constraints to columns that must be unique, using upsert with onConflict to handle conflicts gracefully, and creating partial unique indexes for conditional uniqueness. UNIQUE constraints cause Postgres to reject duplicate inserts with a clear error, while upsert lets you update the existing row instead of failing. Combine these with RLS policies for safe client-side duplicate prevention.

What you'll learn

  • How to add UNIQUE constraints to prevent duplicate column values
  • How to use upsert with onConflict for insert-or-update behavior
  • How to create partial unique indexes for conditional uniqueness
  • How to handle duplicate key errors gracefully in client code
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner8 min read10-15 minSupabase (all plans), @supabase/supabase-js v2+March 2026RapidDev Engineering Team
TL;DR

Prevent duplicate entries in Supabase by adding UNIQUE constraints to columns that must be unique, using upsert with onConflict to handle conflicts gracefully, and creating partial unique indexes for conditional uniqueness. UNIQUE constraints cause Postgres to reject duplicate inserts with a clear error, while upsert lets you update the existing row instead of failing. Combine these with RLS policies for safe client-side duplicate prevention.

Preventing Duplicate Entries with UNIQUE Constraints and Upsert in Supabase

Duplicate data is one of the most common problems in database applications. Supabase uses PostgreSQL, which provides robust tools for preventing duplicates at the database level: UNIQUE constraints, composite unique constraints, upsert operations, and partial unique indexes. This tutorial shows you how to apply each technique, when to use one over another, and how to handle conflict errors in your client code.

Prerequisites

  • A Supabase project (free tier works)
  • Access to the SQL Editor in the Supabase Dashboard
  • @supabase/supabase-js v2 installed in your project
  • A table with RLS enabled and basic policies configured

Step-by-step guide

1

Add a UNIQUE constraint to a single column

The simplest way to prevent duplicates is to add a UNIQUE constraint to the column that must be unique. When you insert a row with a value that already exists in a UNIQUE column, Postgres rejects the insert with a duplicate key violation error. You can add the constraint when creating the table or add it later with ALTER TABLE.

typescript
1-- Add UNIQUE when creating the table
2create table public.subscribers (
3 id bigint generated always as identity primary key,
4 email text unique not null,
5 name text,
6 subscribed_at timestamptz default now()
7);
8
9alter table public.subscribers enable row level security;
10
11-- Or add UNIQUE to an existing column
12alter table public.subscribers
13 add constraint subscribers_email_unique unique (email);

Expected result: Attempting to insert a row with a duplicate email value returns error code 23505 (unique_violation) instead of silently creating a duplicate.

2

Add a composite UNIQUE constraint for multi-column uniqueness

Sometimes a single column is not enough to define uniqueness. For example, in a user_roles table, each user should have each role only once, but multiple users can have the same role and each user can have multiple roles. A composite UNIQUE constraint on (user_id, role) enforces this. The combination must be unique, not each column individually.

typescript
1create table public.user_roles (
2 id bigint generated always as identity primary key,
3 user_id uuid not null references auth.users(id) on delete cascade,
4 role text not null,
5 assigned_at timestamptz default now(),
6 unique(user_id, role)
7);
8
9alter table public.user_roles enable row level security;
10
11create policy "Users read own roles" on public.user_roles
12 for select to authenticated
13 using ((select auth.uid()) = user_id);
14
15create policy "Auth users insert roles" on public.user_roles
16 for insert to authenticated
17 with check ((select auth.uid()) = user_id);

Expected result: Inserting the same (user_id, role) combination twice fails with a unique violation error. Different users can have the same role.

3

Use upsert to insert or update on conflict

Instead of failing on duplicate inserts, you can use upsert to update the existing row when a conflict occurs. The Supabase JS client's upsert method performs an INSERT with an ON CONFLICT clause. Specify the onConflict option to tell Postgres which column(s) to check for conflicts. If a match is found, the existing row is updated with the new values instead of creating a duplicate.

typescript
1import { createClient } from '@supabase/supabase-js'
2
3const supabase = createClient(
4 process.env.NEXT_PUBLIC_SUPABASE_URL!,
5 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
6)
7
8// Upsert: insert new subscriber or update name if email exists
9const { data, error } = await supabase
10 .from('subscribers')
11 .upsert(
12 { email: 'user@example.com', name: 'Updated Name' },
13 { onConflict: 'email' }
14 )
15 .select()
16
17// Bulk upsert: insert or update multiple rows
18const { data: bulk, error: bulkError } = await supabase
19 .from('subscribers')
20 .upsert([
21 { email: 'user1@example.com', name: 'User 1' },
22 { email: 'user2@example.com', name: 'User 2' },
23 { email: 'user3@example.com', name: 'User 3' }
24 ], { onConflict: 'email' })
25 .select()

Expected result: If the email already exists, the name is updated. If the email is new, a new row is inserted. No duplicate key errors are raised.

4

Create a partial unique index for conditional uniqueness

A partial unique index enforces uniqueness only for rows that match a condition. This is useful when you want to allow duplicates in some cases but not others. For example, you might want to enforce unique emails only for active subscribers, allowing deleted or unsubscribed users to have the same email reused by new signups.

typescript
1-- Only enforce unique emails for active subscribers
2create unique index idx_subscribers_active_email
3on public.subscribers (email)
4where is_active = true;
5
6-- Example: add the is_active column first if it does not exist
7alter table public.subscribers
8 add column is_active boolean default true;

Expected result: Two rows with the same email can exist if one has is_active = false. Inserting a second active row with the same email fails with a unique violation.

5

Handle duplicate key errors in client code

When a UNIQUE constraint is violated and you are not using upsert, the Supabase JS client returns an error with code 23505. Handle this error explicitly in your client code to show a user-friendly message instead of a generic error. This is important for forms where users might submit duplicate values.

typescript
1const { data, error } = await supabase
2 .from('subscribers')
3 .insert({ email: 'user@example.com', name: 'New User' })
4 .select()
5
6if (error) {
7 if (error.code === '23505') {
8 // Duplicate key violation
9 console.error('This email is already registered.')
10 // Show user-friendly message in your UI
11 } else {
12 console.error('Insert failed:', error.message)
13 }
14} else {
15 console.log('Subscriber created:', data)
16}

Expected result: Duplicate inserts are caught with a specific error code, allowing you to display a clear message like 'This email is already registered' instead of a generic error.

Complete working example

prevent-duplicates.ts
1import { createClient } from '@supabase/supabase-js'
2
3const supabase = createClient(
4 process.env.NEXT_PUBLIC_SUPABASE_URL!,
5 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
6)
7
8// Insert with duplicate handling
9export async function addSubscriber(email: string, name: string) {
10 const { data, error } = await supabase
11 .from('subscribers')
12 .insert({ email, name })
13 .select()
14 .single()
15
16 if (error) {
17 if (error.code === '23505') {
18 return { success: false, message: 'This email is already registered.' }
19 }
20 return { success: false, message: error.message }
21 }
22
23 return { success: true, data }
24}
25
26// Upsert: insert or update if exists
27export async function upsertSubscriber(email: string, name: string) {
28 const { data, error } = await supabase
29 .from('subscribers')
30 .upsert({ email, name }, { onConflict: 'email' })
31 .select()
32 .single()
33
34 if (error) return { success: false, message: error.message }
35 return { success: true, data }
36}
37
38// Bulk upsert with conflict resolution
39export async function bulkUpsertSubscribers(
40 subscribers: { email: string; name: string }[]
41) {
42 const { data, error } = await supabase
43 .from('subscribers')
44 .upsert(subscribers, { onConflict: 'email' })
45 .select()
46
47 if (error) return { success: false, message: error.message }
48 return { success: true, count: data.length, data }
49}
50
51// Check if a value already exists before inserting
52export async function emailExists(email: string): Promise<boolean> {
53 const { count } = await supabase
54 .from('subscribers')
55 .select('id', { count: 'exact', head: true })
56 .eq('email', email)
57
58 return (count ?? 0) > 0
59}

Common mistakes when avoiding Duplicated Entries in Supabase

Why it's a problem: Relying on client-side checks (query then insert) instead of UNIQUE constraints, creating race conditions

How to avoid: Always enforce uniqueness at the database level with UNIQUE constraints. Client-side checks can miss concurrent inserts from other users.

Why it's a problem: Using upsert without an UPDATE RLS policy, causing the operation to fail when a conflict is found

How to avoid: Add both INSERT and UPDATE RLS policies when using upsert. The upsert performs an UPDATE when a conflict is detected.

Why it's a problem: Adding a UNIQUE constraint to a column that already contains duplicate values, causing the ALTER TABLE to fail

How to avoid: Clean up existing duplicates before adding the constraint. Query for duplicates first: SELECT email, count(*) FROM subscribers GROUP BY email HAVING count(*) > 1.

Best practices

  • Always enforce uniqueness at the database level with UNIQUE constraints, not just in application code
  • Use upsert with onConflict when you want insert-or-update behavior instead of failing on duplicates
  • Add composite UNIQUE constraints when uniqueness depends on multiple columns
  • Handle error code 23505 explicitly in client code to show user-friendly duplicate messages
  • Use partial unique indexes for soft-delete patterns where uniqueness should only apply to active records
  • Clean up existing duplicates before adding UNIQUE constraints to avoid ALTER TABLE failures
  • Add both INSERT and UPDATE RLS policies when using upsert operations
  • Use select('id', { count: 'exact', head: true }) for lightweight existence checks

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I have a Supabase table that keeps getting duplicate entries. Show me how to add UNIQUE constraints, use upsert with onConflict to handle duplicates gracefully, and create partial unique indexes for conditional uniqueness. Include TypeScript client code with error handling.

Supabase Prompt

Create a Supabase subscribers table with a UNIQUE constraint on email. Show TypeScript functions for inserting with duplicate error handling (code 23505), upsert with onConflict, bulk upsert, and a partial unique index that only enforces uniqueness for active records.

Frequently asked questions

What error code does Supabase return for duplicate key violations?

Supabase returns PostgreSQL error code 23505 (unique_violation) when an INSERT or UPDATE violates a UNIQUE constraint. Check error.code in the JS client response.

What is the difference between insert and upsert in Supabase?

Insert creates a new row and fails if a UNIQUE constraint is violated. Upsert creates a new row if no conflict exists, or updates the existing row if a conflict is found on the specified onConflict column.

Can I have multiple UNIQUE constraints on the same table?

Yes. You can add UNIQUE constraints to multiple columns independently. Each constraint is checked separately. For example, both email and username can be unique on a users table.

Does upsert work with composite UNIQUE constraints?

Yes. Pass the composite columns as a comma-separated string: onConflict: 'user_id,role'. The upsert checks the combination of columns for conflicts.

Why does my upsert fail even though the UNIQUE constraint exists?

Upsert requires both INSERT and UPDATE RLS policies. When a conflict is found, the operation switches to an UPDATE, which needs an UPDATE policy. Add one if missing.

How do I find and clean up existing duplicates in a table?

Run: SELECT column_name, count(*) FROM table_name GROUP BY column_name HAVING count(*) > 1. Then delete the extra rows before adding a UNIQUE constraint.

Can RapidDev help design a duplicate-free database schema for my Supabase project?

Yes. RapidDev can audit your existing schema for missing uniqueness constraints, clean up duplicate data, and implement proper upsert patterns to prevent future duplicates.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.