JavaScript query editor
Write real JavaScript against your data
Some questions cannot be expressed as a where clause. FireFetch gives you the genuine Firestore Admin SDK in an editor, so you can just write the code.
JavaScript editor — script running with live log output
screenshot pending · 1400×880
The real SDK, not a subset
Scripts run in a dedicated Node process with the actual@google-cloud/firestoreand firebase-adminpackages loaded. That means the whole surface is available — not a curated handful of methods someone decided you would need.
db, adminFirestore instance and the Admin namespace
FieldValue, TimestampServer transforms and exact timestamps
GeoPoint, FieldPathThe full type set, not stringified
Filter, AggregateFieldComposite filters and aggregations
_ (lodash)Grouping, chunking and reshaping results
fetch, fs, uuid, sleepCall an API, write a file, generate ids
projectId, databaseIdKnow which database you are pointed at
requireNode built-ins and bundled dependencies
Write an async function run(), press Ctrl+Enter, and whatever you return is rendered as documents in the table, tree or JSON view.
async function run() {
// Backfill a denormalised field across every order.
const snap = await db.collection('orders').where('total', '>', 0).get();
const writer = db.bulkWriter();
for (const doc of snap.docs) {
writer.update(doc.ref, {
totalCents: Math.round(doc.get('total') * 100),
migratedAt: FieldValue.serverTimestamp(),
});
}
await writer.close();
console.log(`Updated ${snap.size} orders`);
}A Stop button that actually stops
Because scripts run in their own process rather than on the UI thread, stopping one kills the process. A runawaywhile (true)never freezes the app — you press Stop, it dies, you fix the loop. This is the difference between an editor you trust with production and one you use timidly.
Output that tells you what happened
console.log,console.table,console.groupand console.timestream into a log pane as the script runs, not after it finishes — so a long migration shows progress instead of a spinner.
When something throws, the stack trace points atscript.js:14 — your line numbers, in your code. Not a frame somewhere inside a bundler.
Log pane — streamed console output during a run
screenshot pending · 1200×620
Transactions, batches and bulk writers
Everything the SDK offers works here, including the pieces that are awkward to reach any other way.
async function run() {
// Move credit between two accounts atomically.
const from = db.doc('accounts/alice');
const to = db.doc('accounts/bob');
return db.runTransaction(async (tx) => {
const snap = await tx.get(from);
const balance = snap.get('balance');
if (balance < 100) throw new Error('Insufficient funds');
tx.update(from, { balance: FieldValue.increment(-100) });
tx.update(to, { balance: FieldValue.increment(100) });
return { moved: 100, remaining: balance - 100 };
});
}Return a query and it just runs
A small convenience that removes a lot of friction: return aQuery without calling .get()and FireFetch executes it for you, then renders the results in whichever view you were using.
async function run() {
// No .get() needed — return a Query and FireFetch runs it,
// then renders the documents in the table.
return db
.collection('users')
.where('plan', '==', 'pro')
.orderBy('createdAt', 'desc')
.limit(50);
}Save the ones you will need again
Scripts you keep reaching for belong in the Script Library — named, described and tagged, then run against any project or database you have connected. The one-off cleanup you wrote last quarter is still there when the same thing happens again.