Coming from Supabase
Plexivo Postgres speaks the same Data API and policy language as Supabase. Map supabase-js and supabase_flutter calls, keep your policies, and move.
If you know Supabase, you know Plexivo Postgres. The Data API reads PostgREST query strings, the policy helpers have the same names, so auth.uid() in your policies keeps working, and the client libraries take Supabase’s method names. Your database and its users move with their ids and passwords, and your app runs in Mumbai, with backups copied to Hyderabad.
The client, mapped#
| supabase-js | @plexivo/client |
|---|---|
createClient(url, anonKey) | createClient(projectUrl, publishableKey) |
from(t).select('a, b:c(d)', { count: 'exact' }) | The same |
eq, neq, gt, in, is, ilike, contains, or, not, match, textSearch, filter | The same |
order(c, { ascending, nullsFirst, referencedTable }), range, limit, single, maybeSingle | The same |
insert(v).select(), update(v).eq(…), delete().eq(…), upsert(v, { onConflict, ignoreDuplicates }) | The same |
rpc(fn, args) with filters after it | The same |
const { data, error } = await … | The same with createClient(…, { throwOnError: false }); by default errors are thrown |
auth.signUp({ email, password, options: { data } }) | The same |
auth.signInWithPassword, signInWithOtp, verifyOtp, signInAnonymously, signInWithOAuth, exchangeCodeForSession, updateUser({ data }), onAuthStateChange, signOut | The same |
auth.getSession(), auth.getUser() | The same, returning the session and the user themselves rather than inside data |
channel(n).on('postgres_changes', { table, filter }, cb) | channel('changes:table', { filter }).onChange(cb) |
channel(n).on('broadcast', { event }, cb), track() | channel('room:n', { presence }).onBroadcast(event, cb), onPresence(cb) |
functions.invoke(name, { body }) | The same |
| supabase_flutter | plexivo_client |
|---|---|
Supabase.instance.client | PlexivoClient(projectUrl, publishableKey) |
from(t).select().eq(…).order(c, ascending: false) | from(t).select().eq(…).order(c, 'desc') |
inFilter, isFilter, range, single, maybeSingle | The same |
insert, update, upsert, delete, rpc | The same, plus create that returns the new row |
auth.signUp(email:, password:, data:), auth.signInWithPassword | signUp(email:, password:, data:), signInWithPassword |
auth.onAuthStateChange | authStateChanges |
from(t).stream(primaryKey: ['id']) | channel('changes:t').changes |
The same code#
sql
create table profiles (id uuid primary key default auth.uid(), username text unique not null);
create table posts (
id bigint generated always as identity primary key,
author_id uuid not null default auth.uid() references profiles (id),
title text not null,
published boolean not null default false
);
alter table profiles enable row level security;
alter table posts enable row level security;
-- Policies from Supabase move as they are
create policy "Public profiles" on profiles for select using (true);
create policy "Own profile" on profiles for insert with check (auth.uid() = id);
create policy "Read published or own" on posts for select using (published or auth.uid() = author_id);
create policy "Write own" on posts for all using (auth.uid() = author_id) with check (auth.uid() = author_id);
create function published_count() returns bigint language sql stable security invoker
as $$ select count(*) from posts where published $$;
comment on function published_count() is '@expose';js
import { createClient } from '@plexivo/client';
const supabase = createClient(PLEXIVO_DATA_URL, PLEXIVO_PUBLISHABLE_KEY, { throwOnError: false });
const { error: signUpError } = await supabase.auth.signUp({ email, password, options: { data: { username: 'asha' } } });
await supabase.from('profiles').insert({ username: 'asha' });
const { data: post, error } = await supabase.from('posts').insert({ title: 'Hello', published: true }).select().single();
await supabase.from('posts').update({ title: 'Hello, world' }).eq('id', post.id);
const { data: posts, count } = await supabase.from('posts')
.select('id, title, author:profiles(username)', { count: 'exact' })
.eq('published', true)
.order('id', { ascending: false })
.range(0, 9);
const { data: published } = await supabase.rpc('published_count');
console.log(posts[0].author.username, count, published);dart
import 'package:plexivo_client/plexivo_client.dart';
final supabase = PlexivoClient(dataUrl, publishableKey);
await supabase.signUp(email: email, password: password, data: {'username': 'ravi'});
await supabase.from('profiles').insert({'username': 'ravi'});
final posts = await supabase.from('posts')
.select('id, title, author:profiles(username)')
.eq('published', true)
.order('id', 'desc')
.range(0, 9);What you can do in addition#
- Write queries in GORM’s style when you prefer:
where,preload,joins,whereHas,paginate,firstOrCreate. See Joins. - Order by a related row’s column, and filter by a subquery, from the browser. See Queries.
- Commit several operations from your app in one transaction, with row locks.
- Count, sum and group rows with
aggregateandgroupBy.
Move your project#
- Create the backend. Follow Build a backend in five minutes for a database with the Data API on.
- Bring the database. Move your Supabase database with Migrate to Plexivo Postgres. Your tables, data, functions and policies come with it.
- Bring your users. From your server, with the secret key, call
auth.admin.importFromSupabase(). Every user keeps their id, so rows and policies keep matching, and their password, confirmed email, metadata and Google, GitHub and Microsoft sign-ins keep working. - Switch the app. Change the import to
@plexivo/client(orplexivo_client), and pass your Project URL and publishable key. Rename realtime channels tochanges:<table>.
Next steps#
Updated 28 October 2026