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#

FirebasePlexivo Postgres
Cloud Firestore and Realtime DatabasePostgreSQL tables, read and written through the Data API or GraphQL
A collectionA table
A documentA row; nested maps can live in a jsonb column
A subcollectionA table with a foreign key to its parent, loaded with preload
Security RulesRow-level security policies, using auth.uid()
onSnapshot listenersRealtime channels on a table, or GraphQL live queries
Firebase AuthenticationPlexivo Auth: password, email code, Google, GitHub and Microsoft, guests, authenticator two-step verification
Cloud Functions: callable, HTTP, scheduled, Firestore triggers, user createdPlexivo Functions: called over HTTPS, on a schedule, on row changes, on user sign-up
Transactions and batched writesdb.transaction(), in one PostgreSQL transaction

Code, side by side#

Set up#

js
// Firebase
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const store = getFirestore(app);

// Plexivo
const db = createClient(PLEXIVO_DATA_URL, PLEXIVO_PUBLISHABLE_KEY);

Sign up#

js
// Firebase
await createUserWithEmailAndPassword(auth, email, password);

// Plexivo
await db.auth.signUp({ email, password });

Write#

js
// Firebase
await addDoc(collection(store, 'notes'), {
  owner: auth.currentUser.uid, body: 'Hello', createdAt: serverTimestamp()
});

// Plexivo: owner and created_at fill themselves in (column defaults)
await db.from('notes').create({ body: 'Hello' });

Read#

js
// Firebase
const q = query(collection(store, 'notes'),
  where('owner', '==', auth.currentUser.uid),
  orderBy('createdAt', 'desc'), limit(20));
const snap = await getDocs(q);

// Plexivo: the policy already limits rows to the signed-in user
const notes = await db.from('notes').order('created_at', 'desc').limit(20);

Listen for changes#

js
// Firebase
onSnapshot(q, (snap) => snap.docChanges().forEach((c) => render(c.type, c.doc.data())));

// Plexivo
db.channel('changes:notes')
  .onChange(({ type, record }) => render(type, record))
  .subscribe();

Switch on Realtime for the table on the database’s Data API tab first.

Rules become policies#

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;
--   }

-- Plexivo Postgres
alter table notes enable row level security;
create policy "Each user owns their notes" on notes
  for all to authenticated
  using (owner = auth.uid())
  with check (owner = auth.uid());

Call a function#

js
// Firebase
const { data } = await httpsCallable(functions, 'sendReceipt')({ orderId });

// Plexivo
const { data } = await db.functions.invoke('send-receipt', { body: { orderId } });

Flutter developers use plexivo_client the same way.

Move your app#

  1. Create the backend. Follow Build a backend in minutes: a database, the Data API and the sign-in methods you use today.
  2. 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 an owner uuid default auth.uid() column, so new rows belong to whoever creates them.
  3. 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.
  4. 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.
  5. Load your data. Export your collections to JSON and insert the rows from your server with the secret key, for example with createInBatches.
  6. Move your functions. Rewrite each Cloud Function as a Plexivo Function and give it its secrets on the database’s Functions tab.
  7. 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 27 October 2026