RENXW CRM is a multi-tenant agency CRM that I designed and built as co-founder and technical lead of RENXW. It runs on React, Vite and Supabase, and every one of its 88 permissions is enforced in PostgreSQL row-level security rather than in the interface, with 834 automated tests checking that it stays that way.
The problem
An agency runs on a chain of steps: a lead comes in, someone calls, follow-ups get scheduled, a deal moves through the pipeline, the client is onboarded, a project starts, a website gets built, and the client wants to see progress. Spread across spreadsheets and separate tools, that chain breaks at every handover.
We also wanted the system to serve more than one organisation. That makes it multi-tenant, and multi-tenant changes the stakes: one bug in access control means one client seeing another client's data, including credentials.
Constraints
- Many tenants, one database. Data for different organisations sits side by side and must never leak.
- Fine-grained roles. Agencies have owners, sales, developers, clients and more. We ended up with 15 system roles and 88 permissions.
- Sensitive data. Client logins and keys live in the system, so the vault had to be encrypted.
- Small team. The stack had to be something a small team can deploy and maintain: React and Vite on Netlify, Supabase for the database and auth, GitHub for code.
What I built
Multi-tenant data model
The schema has 86 tables across 32 migrations. Every tenant-owned row carries its tenant, and the row-level security policies use it. Migrations are versioned so the schema can be rebuilt from scratch in any environment.
Permissions in the database
Roles and permissions are data, and RLS policies check them on every read and write. The interface asks the same rules so it can hide what a user cannot do, but the interface is a convenience, not the guard.
Agency workflow
Across 16 screens, the CRM covers leads, calls, follow-ups, pipeline, onboarding, projects, website-build tracking, a client portal and reporting. It also has API integrations and automation hooks so other tools can react to events.
Encrypted credential vault
Client credentials are stored encrypted, behind the same tenant and role rules as everything else.
Test suite
There are 834 automated tests, including 425 tenant-isolation probes that try to cross tenant and role boundaries and fail the build if they succeed.
How it works
- A user signs in through Supabase auth and gets a session tied to their tenant and role.
- The React app requests data directly from Supabase.
- PostgreSQL applies row-level security to every query, checking tenant and permission.
- Rows the user is not allowed to see are never returned, and writes they are not allowed to make are rejected.
- Automation hooks and integrations fire on events such as a new lead or a stage change.
- On every change, the test suite and isolation probes run before anything ships.
Decisions and trade-offs
RLS in the database versus checks in the UI. UI checks are quick to write and easy to read, but they only control what a user sees, not what they can request. With Supabase the client talks to the database directly, so any rule that lives only in React is a suggestion. Putting the rules in RLS means a forged request gets the same answer as the real UI. The cost is that policies are harder to write and debug, and a slow policy slows every query. I accepted that, and wrote about the pattern in Supabase RLS for multi-tenant SaaS.
Permissions as data, not code. Storing 88 permissions in tables means changing a role does not need a deploy. The trade-off is more joins inside policies, which I keep in mind when indexing.
Probes, not just unit tests. Ordinary tests check that features work. Isolation probes check that the wrong person cannot make them work. For a multi-tenant system, the second kind matters more, so 425 of the 834 tests are probes.
Supabase over a custom backend. A hand-written API server would give more control but double the surface to secure. Supabase plus RLS keeps the security model in one place.
Outcome
The CRM is built and running for RENXW with 86 tables, 32 migrations, 15 system roles, 88 permissions enforced in the database, 16 screens and 834 automated tests including 425 tenant-isolation probes. It handles the agency chain from lead to client portal in one system.
What I would do next
Phase 2 is planned in 5 waves: a unified inbox, payments, AI agents, a website builder and white-label support. The white-label wave depends most on the tenant model being right, which is why the isolation work came first. For the other half of RENXW's engineering, the website side, see the RENXW WordPress theme case study.
If you need a CRM or SaaS product built this way, see my custom web app development service or get in touch.