Coming from Firebase
Plexivo Postgres is a Firebase alternative built on PostgreSQL. Map what you know, see the code side by side, and move your app.
If you build on Firebase, you already know how Plexivo Postgres works as a backend: your app talks to it directly, users sign in, rules decide who sees what, and changes arrive live. The difference is underneath. Your data lives in PostgreSQL tables, so you get joins, transactions, constraints and SQL, and it runs in Mumbai, with backups copied to Hyderabad.
The same ideas, mapped#
| Firebase | Plexivo Postgres |
|---|---|
| Cloud Firestore and Realtime Database | PostgreSQL tables, read and written through the Data API or GraphQL |
| A collection | A table |
| A document | A row; nested maps can live in a jsonb column |
| A subcollection | A table with a foreign key to its parent, loaded with preload |
| Security Rules | Row-level security policies, using auth.uid() |
onSnapshot listeners | Realtime channels on a table, or GraphQL live queries |
| Firebase Authentication | Plexivo Auth: password, email code, Google, GitHub and Microsoft, guests, authenticator two-step verification |
| Cloud Functions: callable, HTTP, scheduled, Firestore triggers, user created | Plexivo Functions: called over HTTPS, on a schedule, on row changes, on user sign-up |
| Transactions and batched writes | db.transaction(), in one PostgreSQL transaction |
httpsCallable | db.functions.invoke() |
| Queries across collections, done in app code | Joins: related rows in one request |
count(), sum(), average() | count(), sum(), avg(), and groupBy for reports |
Code, side by side#
A notes app, with the Firebase code in comments above each Plexivo line. First the table and its policy, which replace the collection and its Security Rules:
sql
-- Firebase Security Rules:
-- match /notes/{id} {
-- allow read, update, delete: if request.auth.uid == resource.data.owner;
-- allow create: if request.auth.uid == request.resource.data.owner;
-- }
create table notes (
id bigint generated always as identity primary key,
owner uuid not null default auth.uid(),
body text not null,
created_at timestamptz not null default now()
);
alter table notes enable row level security;
create policy notes_owner on notes for all to authenticated
using (owner = auth.uid()) with check (owner = auth.uid());
create trigger plexivo_realtime after insert or update or delete on notes
for each row execute function auth.realtime_notify('id');Set up and sign up#
js
import { createClient } from '@plexivo/client';
// Firebase: const app = initializeApp(firebaseConfig); const auth = getAuth(app);
const db = createClient(PLEXIVO_DATA_URL, PLEXIVO_PUBLISHABLE_KEY);
// Firebase: await createUserWithEmailAndPassword(auth, email, password);
await db.auth.signUp({ email, password });Listen for changes#
js
// Firebase: onSnapshot(q, (snap) => snap.docChanges().forEach((c) => render(c.type, c.doc.data())));
const feed = db.channel('changes:notes')
.onChange(({ type, record }) => console.log(type, record.body))
.subscribe();
await feed.ready();Write#
js
// Firebase: await addDoc(collection(store, 'notes'), { owner: auth.currentUser.uid, body: 'Hello', createdAt: serverTimestamp() });
// Plexivo: owner and created_at fill themselves in
const note = await db.from('notes').create({ body: 'Hello' });
// Firebase: await updateDoc(doc(store, 'notes', id), { body: 'Hello again' });
await db.from('notes').where('id', note.id).update({ body: 'Hello again' });Read#
js
// Firebase: query(collection(store, 'notes'), where('owner', '==', uid), orderBy('createdAt', 'desc'), limit(20))
// Plexivo: the policy already limits rows to the signed-in user
const notes = await db.from('notes').order('created_at', 'desc').limit(20);
// Firebase: (await getCountFromServer(q)).data().count
const total = await db.from('notes').count();In Flutter#
dart
import 'package:plexivo_client/plexivo_client.dart';
// FirebaseAuth.instance.createUserWithEmailAndPassword(email: email, password: password)
final db = PlexivoClient(dataUrl, publishableKey);
await db.signUp(email: email, password: password);
// FirebaseFirestore.instance.collection('notes').add({...})
await db.from('notes').create({'body': 'Hello from Flutter'});
// ….orderBy('createdAt', descending: true).limit(20).get()
final notes = await db.from('notes').order('created_at', 'desc').limit(20);Move your app#
- Create the backend. Follow Build a backend in five minutes: a database, the Data API and the sign-in methods you use today.
- Design your tables. Give each collection a table and each field a column with a type. Turn subcollections into tables with a foreign key, and keep free-form maps in
jsonb. Wherever a rule checks the user, add anowner uuid default auth.uid()column, so new rows belong to whoever creates them. - Write the policies. Translate each Security Rule into a policy, as above. A table is reachable from your app only once it has row-level security and a policy.
- Bring your users. Import them by email from your server with the secret key, up to 1,000 per call, with a bcrypt password hash where you have one. Users without one sign in with an email code or set a new password through password recovery. Firebase user ids are not UUIDs, so each imported user gets a new id; map their old Firebase id to it as you load their data.
- Load your data. Export your collections to JSON and insert the rows from your server with the secret key, for example with
createInBatches. - Move your functions. Rewrite each Cloud Function as a Plexivo Function and give it its secrets on the database’s Functions tab.
- Switch the app. Replace the Firebase calls with the Plexivo client, test with real accounts, then release.
Keep what worksFirebase Hosting users can serve their web app from Plexivo App Runner, next to the database, in Mumbai.
Next steps#
Updated 28 October 2026