Moving from Supabase

Keep the client. Change what operates it.

Backenly's data plane is PostgREST, so a supabase-js frontend keeps its query grammar. What you gain is the governed change path and the autonomy loop.

Who this is for
Teams on Supabase who want the operator, not a new client
What you already have
A supabase-js frontend and a Postgres schema
What you need
Governance without a frontend rewrite

The problem

Supabase is good infrastructure, which is exactly the issue when nobody on the team wants to own schema design, migrations, and RLS review. The assembly and the upkeep are yours. Switching platforms normally means rewriting every call site, which is why teams stay past the point where it is working for them.

What you would normally build

A rewrite of every data call, a new auth integration, a re-derivation of every security policy in a different dialect, and a migration window long enough to do all three.

What Backenly does

  1. Swap

    Change the import and the client construction

    The compatibility entry point is built on the v2 surface, which passes PostgREST's grammar through untouched. It emits PostgREST rather than translating into a narrower dialect, which is why it is complete rather than approximate.

  2. Keep

    Filters, embeds, upsert, and counts behave

    or(), select('*, author(*)') embeds, overlaps(), upsert with onConflict, and count: 'exact' via Content-Range. Every operation resolves { data, error } and never throws, which is the convention the frontend is written against.

  3. Move

    Bring the data and re-author the policies

    pg_dump in, then take a connection string and run your migrations, then reconcile with adopt_external_schema. Policies are re-authored in Backenly's claim form, where set_rls installs your predicate verbatim and reads pg_policies back before reporting success.

  4. Gain

    The part that was not on offer before

    A governed mutation kernel, behavioural verification after changes, and a loop that monitors and repairs without being asked.

The client swap
// before
import { createClient } from '@supabase/supabase-js'
const supabase = createClient('https://xyz.supabase.co', SUPABASE_ANON_KEY)

// after
import { createClient } from '@backenly/sdk/supabase'
const supabase = createClient(
  'https://backenly.com/api/v1/<PROJECT_ID>',
  BACKENLY_ANON_KEY,
)

// unchanged — this is PostgREST on both sides
const { data, error } = await supabase
  .from('posts')
  .select('*, author(*)')
  .or('published.eq.true,pinned.eq.true')
  .order('createdAt', { ascending: false })

What you end up with

The same query grammar, the same { data, error } contract, and the same realtime subscription shape — over a backend where structural change is governed, verified, and reversible, and where a loop is watching between your deploys.

Who owns what

Backenly does

  • Serves PostgREST's grammar verbatim on the v2 surface, embeds included.
  • Maps auth and postgres_changes subscriptions onto Backenly equivalents.
  • Installs your RLS predicates verbatim and reads them back before reporting success.
  • Refuses what it cannot honour rather than approximating it.

You own

  • The data migration and the cutover window.
  • Re-authoring policies into the claim form, and testing them.
  • Replacing any rpc() call sites — there is no SQL-function surface.
  • Deciding whether the governance trade is worth it for your team.

What this is built on

CapabilityWhat it does here
@backenly/sdk/supabaseCompatibility entry point emitting PostgREST directly, with { data, error } semantics and no throws.
/api/v2/{projectId}/{table}PostgREST grammar passed through untouched — filters, ordering, embeds, Prefer headers, Content-Range counts.
set_rlsPolicies as exact SQL, installed verbatim, read back from pg_policies. No model in the path.
pg_dumpFull schema export in either direction, so the move is reversible.
Autonomy loopContinuous monitoring and repair of the reversible safe band — the capability that has no Supabase equivalent.

Known limitations

  • rpc() is refused. Backenly exposes no SQL functions by design, so any stored-procedure call sites need re-homing as event, cron, or HTTP functions.
  • Policies must be re-authored. They are not translated automatically from Supabase's dialect.
  • Storage and auth are Backenly's implementations, not Supabase's. The compat layer maps the common auth calls; anything provider-specific needs checking.
  • This is a client-compatibility bridge, not a one-click migration. Moving data and cutting over remain manual work.

Common questions

Does my existing query code really work unchanged?

The common path does, because both sides are PostgREST clients and the mapping is largely the identity function. Filters, column projection, embedded resources, upsert with onConflict, and exact counts pass through. Test rpc() call sites and anything provider-specific before cutting over.

What about realtime subscriptions?

channel().on('postgres_changes', …) maps onto Backenly realtime, which is PostgreSQL LISTEN/NOTIFY over Server-Sent Events through a shared listener hub. No WebSocket server and no Redis on this side.

Why would I move at all?

Only one reason is worth the work: you want the operating layer. Supabase hands you excellent primitives and you own the assembly and upkeep. Backenly governs every structural change, verifies it behaviourally, and runs a repair loop between your deploys. If your team enjoys owning that, Supabase is a good product and you should stay on it.

Try it on one free project

No credit card. Connect your agent over MCP and judge it by the verification evidence.