Docs
Everything you need to switch and build.
Estra speaks the Firestore API, so most of what you know still applies. These pages cover what's different: installing the packages, pointing an app at Estra, moving data and keeping your rules.
Install
Six packages on npm.
| Package | Use it for |
|---|---|
@estra/create | Starting a new app: npm create @estra writes the starter's schema, rules and a client example per platform. |
@estra/client | Browsers, Node and edge runtimes: documents, queries, live listeners, auth, files, callable functions, and a Firebase-compatible API. No dependencies. |
@estra/admin-node | Servers and scripts: firebase-admin's Firestore and Auth API, running as admin with your service key. |
@estra/schema | Your schema.ts: defineSchema, field types and npx estra migrate. |
@estra/import | Moving off Firebase: draft a schema, copy and verify data, import users with their password hashes. |
@estra/rules-firestore | Your firestore.rules and storage.rules: check them, compile them, keep them. |
npm install @estra/client
npm install @estra/admin-node
npm install -D @estra/schema
Start a new app
A backend from a starter, code from one command.
No Firebase needed. In the dashboard, create a project, choose Starting fresh and a starter: Blank (a profiles collection with owner-only rules) or Social (profiles, posts and comments with public read and owner write, a query, an index and a live feed). Your backend is built from the starter's schema and rules, with email-link and password sign-in working from the first minute. Then, in your repository:
npm create @estra@latest estra -- --url https://my-app.estra.cloud --starter social --platforms web,server
npm i @estra/client
It writes schema.ts and firestore.rules (the same files your backend runs), estra.json, .env.example, and one example per platform: the client connection, a sign-up, a write and a live query. To change your data model, edit the two files and push them. The backend migrates itself in seconds, with no deploy; removing a field needs --allow-destructive, because it deletes data.
cd estra && npm install # once: the folder npm create @estra wrote
npm run push:check # the SQL it would run
npm run push
Pushing from a folder of your own? It needs a package.json with "type": "module" and @estra/schema installed; run npx --no estra push there. The URL comes from estra.json or ESTRA_URL, the service key from ESTRA_KEY in .env.
Web apps
Keep the Firebase SDK calls, change the import.
If your app uses the Firebase web SDK, alias its modules to Estra's compatible build in your bundler and pass your Estra URL to initializeApp. getAuth, getFirestore, onSnapshot, runTransaction, signInWithPopup and the rest keep working, and errors keep their auth/… and Firestore codes.
// vite.config.ts, next.config.js, webpack …
resolve: {
alias: {
"firebase/app": "@estra/client/compat/app",
"firebase/auth": "@estra/client/compat/auth",
"firebase/firestore": "@estra/client/compat/firestore",
"firebase/storage": "@estra/client/compat/storage",
},
}
// Where you call initializeApp today
const app = initializeApp({ estraUrl: "https://my-app.estra.cloud" });
New apps can use @estra/client/firestore and @estra/client/auth directly: the same modular names, with an offline cache and latency compensation built in. Callable functions live in @estra/client/functions, and presence, broadcast and typing indicators in @estra/client/channels.
Servers and scripts
firebase-admin, pointed at Estra.
Every call carries your project's service key, so it runs as admin and rules don't apply. Keep it on servers.
import { initializeApp, getFirestore, getAuth, FieldValue } from "@estra/admin-node";
initializeApp({ url: "https://my-app.estra.cloud", serviceKey: process.env.ESTRA_SERVICE_KEY });
const db = getFirestore();
await db.collection("posts").doc(id).update({ likes: FieldValue.increment(1) });
const snap = await db.collection("posts").where("published", "==", true).orderBy("at", "desc").limit(50).get();
await getAuth().setCustomUserClaims(uid, { role: "editor" });
Covered: references, get / set / update / delete, queries with cursors, count() and aggregate(), batches, runTransaction, and the Auth user, claims, token and session-cookie methods.
Migrate from Firebase
Three commands, then a host change.
The import tool runs on your machine with a Firebase service account and never writes to Firebase. Your credentials stay with you.
-
01
Draft a schema
It samples every collection and writes a typed
schema.tsfor you to review: fields, arrays, nested maps, subcollections and your composite indexes.npx @estra/import schema --project my-app \ --credentials sa.json --indexes firestore.indexes.json --out schema.ts -
02
Copy, verify, import users
Copy every collection, compare the two sides, and bring users over with their password hashes so nobody resets anything. Top up with
--sinceuntil you switch.npx @estra/import data --project my-app --credentials sa.json --schema schema.ts --target https://my-app.estra.cloud --key $ESTRA_SERVICE_KEY npx @estra/import verify --project my-app --credentials sa.json --schema schema.ts --target https://my-app.estra.cloud --key $ESTRA_SERVICE_KEY --random npx @estra/import users --project my-app --credentials sa.json --target https://my-app.estra.cloud --key $ESTRA_SERVICE_KEY -
03
Switch hosts
Alias the SDK as above, or point a REST client's base URL at Estra: it serves the Firestore, Identity Toolkit and Secure Token APIs too. Firestore stays untouched as your rollback.
- https://firestore.googleapis.com/v1/projects/my-app/… + https://my-app.estra.cloud/v1/projects/my-app/…
Hosted projects get all of this from the dashboard: upload schema.ts and firestore.rules, and it hands you the commands with your URL and keys filled in.
Schema
Typed collections, real indexes.
Estra is schema-first: each collection declares its fields, and a write that names an undeclared field is refused. Nested maps become queryable dotted paths, arrays can be indexed for array-contains, and geo fields power radius queries.
import { collection, defineSchema, f } from "@estra/schema";
export const schema = defineSchema({
posts: collection({
fields: {
title: f.string({ required: true }),
authorId: f.string(),
published: f.bool(),
at: f.timestamp(),
tags: f.array("string", { indexed: true }),
stats: f.map({ views: f.int(), likes: f.int() }),
},
indexes: [["authorId", ["at", "desc"]], ["published", "at"]],
sub: { comments: collection({ fields: { body: f.string(), by: f.string() } }) },
}),
});
npx estra migrate --schema schema.ts --out migrations turns a schema change into the next migration. Hosted projects apply it for you.
Security rules
Your firestore.rules, as is.
Rules version 2 is supported: nested match with wildcards, functions with let, request.auth and request.resource, get / exists lookups, and the string, list, map, set, timestamp, duration and math libraries. Queries are checked the way Firestore checks them, from their filters, so an allowed query costs nothing extra.
npx @estra/rules-firestore check firestore.rules
firestore.rules:41: warning: method .capitalize() isn't supported yet
ok (1 warning(s))
Prefer TypeScript? Native rules are plain functions over the same request object, and the two formats can be mixed per collection.
Resend-compatible, same project.
Transactional email is part of the platform. The POST /emails request, response and errors match Resend, so the Resend SDK works as is with two environment variables changed. Sign-in links and password resets go out through it automatically.
RESEND_BASE_URL=https://api.estra.cloud
RESEND_API_KEY=em_…
Push notifications
iPhone and Android, one request.
Each project has its own push: add your app's Apple key and Firebase service account under Push › Apps in the console, create a key, and send from your server. Retries, dead-token reporting and daily totals are built in.
POST https://api.estra.cloud/v1/push/send
Authorization: Bearer ep_…
{ "tokens": [{ "platform": "ios", "token": "…" }], "title": "Hello", "body": "From your app" }
The console
Laid out like Firebase, with your numbers.
Everything sits where the Firebase console taught you to look: Database (Data, Rules, Indexes, Schema, Usage), Authentication (Users, Sign-in method, Templates, Settings), Storage, Realtime, Mail and Push, with Project settings behind the gear. Press ⌘K to jump anywhere.
The Overview shows requests per minute as they happen, your active users and a checklist of what to set up next. Usage shows document reads, writes and deletes the way Firestore counts them, against a daily allowance that is free during the beta. The rules playground runs a request against your live rules and data without writing anything, and highlights the line that decided. Once a week, if you ask for it, a digest email tells you how your app did.
Reference
The rest of the story.
Each package's README on npm covers its API. The full reference, including the HTTP and WebSocket protocol, realtime patterns, triggers, file storage, push notifications and the design decisions, lives in the repository.