Skip to content
← Features

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

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, admin

    Firestore instance and the Admin namespace

  • FieldValue, Timestamp

    Server transforms and exact timestamps

  • GeoPoint, FieldPath

    The full type set, not stringified

  • Filter, AggregateField

    Composite filters and aggregations

  • _ (lodash)

    Grouping, chunking and reshaping results

  • fetch, fs, uuid, sleep

    Call an API, write a file, generate ids

  • projectId, databaseId

    Know which database you are pointed at

  • require

    Node 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.

Backfill with a bulk writer
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

Transactions, batches and bulk writers

Everything the SDK offers works here, including the pieces that are awkward to reach any other way.

Atomic transfer inside a transaction
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.

Returning a Query
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.

Related