Switch language:

How I Built an App That Syncs With Supabase Without Overwriting Data - 02/03/2026

One of the hardest parts of building a collaborative or real-time app is keeping data in sync across multiple users without one client's update overwriting another's changes. In this post I explain how I solved that problem using Supabase.

Live app: capturioo.com

One of the hardest parts of building a collaborative or real-time app is keeping data in sync across multiple users or sessions without one client’s update overwriting another’s changes. In this post, I explain how I solved that problem using Supabase as the backend.


The Problem: Sync vs. Overwrite

In a traditional app using fetch or axios, the typical flow is:

  1. Client fetches data from the server.
  2. User changes something.
  3. Client sends the full object back to the server.

The problem is that in step 3, if another user modified the same resource in the meantime, those changes are lost. This is called a blind overwrite.


The Solution: Granular Updates + Real-Time

Instead of sending the full object, the key is to send only the field that changed. I call this a semantic patch.

Example:

// ❌ Wrong — overwrites everything
await supabase
  .from('items')
  .update({ ...fullObject })
  .eq('id', item.id);

// ✅ Right — only updates the changed field
await supabase
  .from('items')
  .update({ title: newTitle })
  .eq('id', item.id);

Supabase Realtime: Listening to Changes Without Polling

With supabase.channel() I subscribe to table changes in real time. When another user modifies a record, my client receives the event and only merges that field into local state, without replacing the entire object:

supabase
  .channel('items-channel')
  .on(
    'postgres_changes',
    { event: 'UPDATE', schema: 'public', table: 'items' },
    (payload) => {
      // Merge only the changed field, not replace the full object
      setState((prev) =>
        prev.map((item) =>
          item.id === payload.new.id
            ? { ...item, ...payload.new }
            : item
        )
      );
    }
  )
  .subscribe();

Row Level Security (RLS): Protect Without Blocking

Supabase uses PostgreSQL with RLS. I configured policies so each user can only modify their own records, but can read others’ in collaborative contexts. This ensures that even with open sync, private data stays protected.


Lessons Learned


You can see this architecture in action at capturioo.com.