Supabase vs Appwrite: The Real Divide Isn't the Database, It's the Permission Model
Appwrite grants roles; Supabase evaluates a SQL predicate per row. An audit of what our own RLS policies would have to become as a Function instead.
Both are open-source backends you can point at either a managed cloud or your own Docker host, so "open source" doesn't decide it. The real split is what a permission can express: Supabase's Row-Level Security is a SQL predicate — auth.uid() = user_id, joins and all — evaluated by Postgres on every connection. Appwrite grants access by role at the collection or document level and, by its own contributors' admission, has no equivalent to a conditional USING clause. That difference is invisible until you try to port a real policy, which is what this post does with ours.
The decision in one table
| Axis | Supabase | Appwrite |
|---|---|---|
| Database | PostgreSQL, relational | Its own document-oriented store (MariaDB-backed internally) |
| Query model | SQL — joins, views, CTEs | Collection queries via SDK/REST; no join, no raw SQL |
| Authorization | Row-Level Security: a SQL predicate, evaluated by the database | Role-based grants at the collection or document level |
| Conditional permissions | Yes — using (auth.uid() = user_id), any boolean SQL | No native equivalent — attribute-conditional checks move to a Function |
| Self-hosting | Supported, not the primary distribution | The headline feature — Docker-first |
| Server logic | SECURITY DEFINER Postgres functions | Appwrite Functions, containerized, per-execution billing on cloud |
| Client access | PostgREST gateway + typed JS SDK | REST/GraphQL/Realtime APIs + SDKs |
| Realtime | Postgres replication, per-table | Native, event-based, built in from the start |
| SSR fit for Next.js | Native — cookie-based SSR client | Workable via SDK, less idiomatic for Server Components |
We run our commerce layer on Supabase and have not rebuilt it on Appwrite, so nothing here is a performance benchmark of either platform. What follows is what our own policies would have to become, checked against Appwrite's documented permission model and its own open feature requests.
Where the real divide is, and it isn't "Postgres vs. NoSQL"
That axis is already covered for this catalog — Supabase vs. MongoDB is the relational-vs-document post, and Supabase vs. Firebase is the Security-Rules-vs-RLS one, where Firebase's purpose-built rules language can express a conditional predicate (resource.data.userId == request.auth.uid) even though it lives outside the database. Appwrite's permission model can't make that comparison, because it isn't predicate-based at all.
This site's six RLS policies are all one shape:
create policy "own profile" on public.profiles for select using ((select auth.uid()) = id);
create policy "own orders" on public.orders for select using ((select auth.uid()) = user_id);
create policy "own slots" on public.entitlement_slots for select using ((select auth.uid()) = user_id);
create policy "own downloads" on public.download_events for select using ((select auth.uid()) = user_id);
Every one of them is a using clause: a boolean expression Postgres evaluates per row, per query, against whatever column the row actually holds. There is no write policy for any signed-in role anywhere in this schema — every mutation goes through a security definer RPC instead, which is the same "read-own-only, no client writes" posture the Firebase post documents in full.
Appwrite's permissions run differently. A collection or an individual document is granted to a role — user:<id>, team:<id>, any, label:<name> — and a request either holds that role or it doesn't. There is no SQL-style predicate evaluated against the row's own columns at query time. The gap is not hypothetical: an open Appwrite feature request, issue #5974, asks for exactly this — restricting a document by "its attribute value" or "any relative relationship query," explicitly naming Supabase's RLS as the bar Appwrite doesn't clear, because "can't do based on its attribute value" is the requester's own words for the limitation. It's still open.
Translate that into what our schema needs: entitlement_slots rows are readable by their user_id. Under Appwrite's model, that's the easy case — grant the document to user:<id> at write time and the role check does the rest. It gets harder the moment the rule isn't "owned by," which for us it usually isn't: digital-product-refund-policy walks through RefundIneligibleReason, a four-state check computed from other rows — has this product already been downloaded, is it inside the refund window, does a slot exist at all. None of that is a role a document can hold in advance; it's a query result. On Postgres it's a using clause that reads other tables. On Appwrite, per the model above, that logic has nowhere to live except a Function — imperative code you write, test and deploy yourselves, doing at request time what a database-level predicate does for free.
The self-hosting argument, and why it isn't ours to make
Being fair to the side that doesn't favor us: Appwrite's actual pitch is Docker-first self-hosting, and on that specific axis it's the more committed product. Supabase is technically self-hostable too, but its managed cloud is the primary distribution, and this storefront runs on that managed cloud — configured through the Supabase dashboard, migrations applied via the Supabase CLI, the service-role key issued from the console. We get Postgres's portability in principle (the Neon post covers exactly what a Postgres-to-Postgres move would cost) without ever having exercised the self-host path ourselves.
That matters for anyone actually choosing on this axis: if the reason you want an open-source backend is "I intend to run this on my own hardware, today," Appwrite is built around that being normal, and Supabase's self-hosting story — while real — is a secondary path relative to its cloud product. If the reason is "I want the option to leave," both qualify, and the harder question is the one above: which one already expresses the access rules you'd have to keep.
What the SDK costs, measured once and cited rather than re-derived
One number carries over regardless of which BaaS wins the argument: the Supabase browser client's chunk size, measured from this repository's own production build — 245,083 bytes raw / 63,579 bytes gzipped, and on the first-load path of zero of this site's routes after src/lib/supabase/lazyClient.ts deferred the import behind a cookie probe. Appwrite's Web SDK ships a comparable client-fetch wrapper; we have not measured it on an equivalent build, so we're not claiming a number for it — only naming that any client SDK for either platform has a size, and the fix (defer the import to where it's actually used) is a platform-independent lesson, not a Supabase one.
Where Appwrite is straightforwardly the better answer
- You are actually self-hosting, on day one. Appwrite's Docker Compose setup, its own admin console, and its Functions runtime are one integrated product built for that; assembling the equivalent around self-hosted Supabase is more work.
- Your access rules really are just "who owns this." If every rule in your app is "the current user" or "a member of this team" — no cross-table conditions, no computed eligibility — Appwrite's role grants cover it natively and you never reach for a Function.
- You want one first-party product for auth, storage, functions and realtime with a single admin UI, rather than Postgres plus a set of first-party services layered onto it.
Mistakes and how they show up
| Mistake | How it shows up | Fix |
|---|---|---|
| Assuming Appwrite permissions can express a conditional rule | The document either has the role or doesn't; there is no way to encode "unless the other table says otherwise" | Move the conditional logic into an Appwrite Function, and budget for writing and testing it yourself |
| Comparing the two only as "SQL vs. NoSQL" | Misses that Firebase's Security Rules can express conditionals Appwrite's grants can't | Compare on what the permission layer can evaluate, not on the database's shape |
| Choosing Appwrite for self-hosting, then not self-hosting it | Pays the operational cost of Docker without the independence that justified it | Self-host if that's the actual reason; use the managed cloud otherwise and reconsider the platform choice on its other merits |
| Treating an unmeasured vendor's bundle as equivalent to a measured one | A comparison post states a number for one side and a guess for the other | Say plainly which numbers are measured and which aren't, as this post does |
| Writing row-owner logic as a Function "for now" | Grows into unaudited business logic scattered across serverless handlers | If the rule is relational, that's the argument for a relational database's own enforcement layer |
Frequently asked questions
Is this a benchmark of Supabase against Appwrite? No. We run Supabase and have not rebuilt this stack on Appwrite, so there's no performance claim for either. What's measured is our own dependency surface — six RLS policies, all predicate-based — checked against what Appwrite's documented permission model and its own open feature requests say it can and can't express.
Can Appwrite do row-level security at all?
It has row-level (document-level) permissions, but they're role grants, not conditional predicates. You can restrict a document to a specific user or team; you can't write the equivalent of using (organisation_id = current_user_org()) without moving that check into a Function.
Does self-hosting change this comparison? It changes the self-hosting argument, not the permission-model one. Appwrite's Docker-first design is real and Supabase's self-host path is secondary to its managed cloud — but neither changes what a permission on either platform can evaluate once deployed.
What about Firebase instead of Appwrite? Different axis, and it has its own post. Firebase's Security Rules are a purpose-built conditional language, so that comparison is RLS-vs-Security-Rules — two ways to express a predicate. Appwrite vs. Supabase is predicate-based vs. role-based, a step further apart.
Templates in this post
ASoc Iris is a computer-vision studio site — vision capabilities, case studies and pricing, the kind of product page where "who can see this project" is exactly the row-level question this post is about. ASoc Ledger is a finance-app marketing site with live dashboards and 50+ integrations, where account-scoped data is the whole premise. ASoc Magnet is a lead-capture SaaS site — channel insights, capabilities and testimonials — the shape of product a team wires a backend into first.
Browse the full sets: Next.js landing page templates, Tailwind landing page templates. For what our own RLS policies protect and how the RPCs that write to them stay atomic, Supabase vs. Neon has the full schema audit. For the two bundled-backend suites a layer up from both, AWS Amplify vs. Firebase weighs Cognito and AppSync against Firebase Auth and Firestore.
