INGENIERÍA

Compartir por Organización: Un Equipo, Una Vista, Cero Silos

Andres MuguiraFebrero 17, 20266 min de lectura
OrganizaciónCompartirReal-timeEquipoBase de Datos
← Volver al Blog
Resumir con IA

The Silo Problem

SalesSheet started as a single-user CRM. When it was just me using it, user-scoped data made perfect sense. My contacts, my deals, my pipeline. The database queries were simple: WHERE user_id = auth.uid(). The Row Level Security policies were straightforward. The AI assistant searched my records and only my records. Everything was clean and fast.

Then teams started requesting access. The moment a second person joins your CRM, user-scoped data becomes a liability. Two reps could be calling the same prospect without knowing it. Pipeline data was fragmented across accounts, making forecasting impossible. The AI search only returned your personal records, so asking "what do we know about Acme Corp?" gave different answers depending on who was asking. Notes, call logs, and email threads were invisible to teammates who needed context before their own conversations with the same company. This had to change.

A CRM with siloed data is not a CRM. It is a collection of personal address books that happen to share a login page. Real CRM starts when the entire team sees the same truth.
Before: user-scoped view vs. After: org-scoped view

What Gets Shared

When we designed org-wide sharing, we had to decide what belongs to the organization and what stays personal. The answer was more nuanced than "share everything." Contacts, companies, deals, pipeline stages, and activity history are all org-scoped. When anyone on the team adds a contact, everyone can see it. When a deal moves from Proposal to Negotiation, every team member's pipeline view updates in real time. Call logs, email threads linked to contacts, and notes are visible to the entire team.

But not everything is shared. Saved views remain personal by default because each rep has their own workflow and filter preferences. Notification settings stay user-scoped so you are not bombarded with alerts for deals you do not own. AI conversation history with the assistant is private because those queries often include strategic thinking that is not ready to share. We added a "created_by" field to every shared record so you can always see who added a contact or logged a note, preserving individual accountability within the shared dataset.

The Engineering Challenge: Rewriting RLS Policies

Moving from user-scoped to org-scoped data meant rewriting every Row Level Security policy in our Supabase database. RLS is the layer that enforces data access at the PostgreSQL level, below our application code. The old policies were simple: auth.uid() = user_id. The new policies need to resolve the user's organization membership and then check organization_id = user_org_id.

The challenge is performance. A naive RLS policy that joins the users table on every row check adds a subquery to every SELECT, INSERT, UPDATE, and DELETE operation. At scale, this becomes a bottleneck. We solved it by storing the organization_id as a custom claim in the Supabase JWT. When a user logs in, our auth hook looks up their organization and adds {"organization_id": "org_abc123"} to the JWT claims. The RLS policy then reads from the JWT instead of querying the users table: auth.jwt()->'app_metadata'->>'organization_id' = organization_id. This makes the policy evaluation a simple string comparison with zero additional queries. We wrote about our broader production hardening approach that includes RLS testing as part of our CI pipeline.

Row Level Security in Supabase is the most underappreciated feature for multi-tenant SaaS. It enforces data isolation at the database level, making it impossible for application-layer bugs to leak data between organizations.

Real-Time Sync Goes Org-Wide

Contact timeline showing activities from multiple team members

The real-time subscription upgrade was the trickiest part of the entire migration. In the single-user model, each client subscribed to changes on rows where user_id = currentUser. Supabase Realtime filters these events server-side using RLS, so each client only received events for their own data. When we switched to org-scoped RLS policies, every team member suddenly received events for all org data. This was correct behavior, but it introduced two problems we had not anticipated.

First, the event volume increased dramatically. A five-person team generates roughly five times the real-time events compared to a single user. Each client now processes events from all teammates, not just their own. We added client-side event batching that collects incoming events for 100ms before applying them to the React Query cache, reducing re-renders from five individual updates to one batched update. Second, echo events became more confusing. When you update a deal, you get your own optimistic update immediately, then the server confirms via REST, then the real-time subscription delivers the same change as a WebSocket event. Without echo prevention, the deal card would flicker through three render cycles. We implemented the dedup window pattern where each client tracks its own recent writes by row ID and ignores real-time events for rows it modified within the last 2 seconds.

The Permissions Model

Org-wide sharing does not mean org-wide chaos. We implemented a three-tier permissions model: Owner, Member, and Viewer. Owners can manage organization settings, invite and remove members, and delete any record. Members can create, edit, and delete records they own, and edit records owned by others (with the edit attributed to them in the activity log). Viewers can see all org data but cannot modify anything. This covers the most common team structures: the founder or sales manager as Owner, active reps as Members, and executives or advisors who just want pipeline visibility as Viewers.

Permission checks happen at both the application layer and the database layer. The UI hides edit buttons and delete actions for Viewers, providing a clean read-only experience. But we do not rely on UI-level checks alone. The RLS policies also enforce permissions, so even if someone manipulates the client-side code to bypass the UI restrictions, the database rejects the write. This defense-in-depth approach is the same philosophy behind our server-side AI security architecture: never trust the client, always verify server-side.

Migrating Existing Users to Org-Scoped Data

The migration from single-user to org-scoped data was the part that kept us up at night. Existing users had contacts, deals, and activity history stored with user_id as the ownership key. We needed to assign every existing record to an organization without breaking anything. The migration ran in three phases.

Phase one created an organization for every existing user and made them the Owner. Phase two added an organization_id column to every data table and backfilled it from the user's organization membership. Phase three switched the RLS policies from user_id to organization_id. We ran the entire migration in a transaction so that if any phase failed, everything rolled back. The migration completed in under 4 seconds for our largest test dataset (12,000 contacts, 3,000 deals, 45,000 activity entries). We also added a fallback: if the new RLS policies returned fewer rows than the old ones for any user during the first week, we logged a warning for manual review. Zero warnings fired.

The Impact on Daily Team Selling

The before and after is stark. Before org sharing, our beta team of four had 340 duplicate contacts across their individual accounts. The same prospect appeared in two or three separate user databases with different notes and conflicting deal stages. After enabling org sharing and running a deduplication pass, they had 210 unique contacts with complete, merged activity histories. The AI assistant could finally answer "when was the last time anyone on our team spoke to this person?" instead of only knowing about the asking user's interactions.

Pipeline visibility was the other major unlock. The sales manager could now see every deal across the team in a single pipeline view, filter by rep, sort by value, and identify stale deals that needed intervention. Before, they had to ask each rep for a status update or export individual CSVs and merge them in a spreadsheet. The real-time analytics dashboard now aggregates across the org, showing total pipeline value, conversion rates by stage, and activity trends for the entire team. If you are a small sales team still using individual CRM accounts, the transition to shared org data is the single highest-leverage change you can make. It turns a collection of personal tools into an actual team platform.

Prueba SalesSheet Gratis

Sin tarjeta de crédito. Comienza a vender de forma más inteligente hoy.

Comenzar Prueba Gratis