<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Cengal]]></title><description><![CDATA[Cengal]]></description><link>https://cengal.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6ad6d7797978c30ed50af693/be714f98-16eb-4d8e-975b-82fb2580df06.png</url><title>Cengal</title><link>https://cengal.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Sun, 25 Oct 2026 09:44:35 GMT</lastBuildDate><atom:link href="https://cengal.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Deleted a Firestore document, but its children are still there?]]></title><description><![CDATA[Reproduce parent-only deletion with two local emulator documents. Inspect surviving child paths and choose an explicit cleanup boundary.]]></description><link>https://cengal.hashnode.dev/deleted-a-firestore-document-but-its-children-are-still-there</link><guid isPermaLink="true">https://cengal.hashnode.dev/deleted-a-firestore-document-but-its-children-are-still-there</guid><category><![CDATA[Firebase]]></category><dc:creator><![CDATA[Cengal]]></dc:creator><pubDate>Thu, 22 Oct 2026 02:56:04 GMT</pubDate><content:encoded><![CDATA[<p>Deleting a parent document with the SDK does not mean deleting the tree below it. A missing parent and readable child documents can both be real at the same time.</p>
<p>Disclosure: This AI-assisted technical article was prepared for Cengal, a Firestore developer tool. All examples and original code-drawn illustrations use synthetic data. No Cengal account, subscription or paid product is needed to follow it.</p>
<h2>A reported symptom, not a diagnosis of someone else's system</h2>
<p>On June 24, 2021, a developer <a href="https://stackoverflow.com/questions/68113819/firestore-delete-non-existent-ancestor-documents-and-sub-collection">reported on Stack Overflow</a> that deleted team documents left player documents behind, while querying teams did not reveal those missing parents.</p>
<p>That report includes cleanup-function code and later errors. We have not reproduced that entire application or established its complete cause. The narrower behavior is documented by <a href="https://firebase.google.com/docs/firestore/manage-data/delete-data">Firebase's deletion guide</a>: a document deletion does not cascade into its subcollections. An absent parent is not returned as a document in a normal parent-collection query.</p>
<p>This is worth checking before treating a remaining child as a failed delete or a stale UI.</p>
<img src="https://cengal.app/images/guides/delete-boundary.png" alt="Synthetic path diagram: teams/demo is absent after deleting the parent, while teams/demo/players/ada remains readable." style="display:block;margin:0 auto" />

<h2>Reproduce it with two disposable documents</h2>
<p>Use the Node.js <strong>server SDK</strong> <code>@google-cloud/firestore</code> in a scratch project. Start a local Firestore emulator on <code>127.0.0.1:8080</code> with project ID <code>demo-delete-example</code>, following <a href="https://firebase.google.com/docs/emulator-suite/connect_firestore">Firebase's emulator connection guide</a>. Set <code>FIRESTORE_EMULATOR_HOST=127.0.0.1:8080</code> before running this CommonJS script.</p>
<pre><code class="language-javascript">const { Firestore } = require('@google-cloud/firestore');
if (process.env.FIRESTORE_EMULATOR_HOST !== '127.0.0.1:8080') {
  throw new Error('This example requires the local Firestore emulator');
}
const db = new Firestore({ projectId: 'demo-delete-example' });

async function run() {
  const team = db.doc('teams/demo');
  const player = team.collection('players').doc('ada');
  await team.set({ name: 'Demo team' });
  await player.set({ name: 'Ada' });

  await team.delete(); // deletes this document, not its descendants
  console.log((await team.get()).exists); // false
  console.log((await player.get()).exists); // true
  console.log((await db.collection('teams').get()).size); // 0
  console.log((await team.collection('players').get()).size); // 1

  await player.delete(); // clean up this one known synthetic child
  console.log((await player.get()).exists); // false
}
run().catch(error =&gt; {
  console.error(error);
  process.exitCode = 1;
}).finally(() =&gt; db.terminate());
</code></pre>
<p>The guard deliberately refuses a different or missing emulator endpoint. The <code>demo-</code> project ID is another boundary. Do not adapt this example by pointing it at a real team or account. If you choose another loopback port, change both the environment value and the guard.</p>
<h2>Two paths, two outcomes</h2>
<p>The parent <code>teams/demo</code> stores one document's fields. The child <code>teams/demo/players/ada</code> is a separate document at another path. Removing the first does not remove the second. A reference to the child still identifies its path even when the parent document no longer exists.</p>
<p>The empty <code>teams</code> query therefore answers a narrow question: which existing team documents match? It is not an inventory of every descendant stored beneath team paths. Check a known child reference separately when investigating this symptom.</p>
<img src="https://cengal.app/images/guides/delete-reaction.png" alt="Original cartoon: a surviving child document smiles and says: I still live here. The takeaway is that one document delete is not a tree delete." style="display:block;margin:0 auto" />

<p>Animation note: this syndicated copy uses an equivalent still. The <a href="https://cengal.app/guides/firestore-delete-parent-subcollections/">original article</a> provides the original reaction animation with pause and reduced-motion support.</p>
<h2>Choose the intended deletion boundary</h2>
<p>If you meant to delete only the parent record, surviving children may be expected. Decide whether the application should keep or expose them; do not infer the intended retention policy from the document hierarchy alone.</p>
<p>If you meant to remove a whole subtree, use a separately designed cleanup operation. <a href="https://firebase.google.com/docs/firestore/solutions/delete-collections">Firebase's recursive-deletion solution</a> describes a trusted-server approach and warns about authorization, cost and incomplete work. Review that guidance rather than turning the tiny example above into a general recursive-deletion utility.</p>
<p>For a real cleanup, first inventory the exact project, database and paths. Restrict who can request it, coordinate other writers, choose recovery procedures, and make progress and failures visible. Reading and deleting a changing dataset is not automatically atomic: another writer can create children while cleanup runs. A successful parent delete alone proves nothing about completion of a larger cleanup.</p>
<h2>What the local test actually proved</h2>
<p>On October 22, 2026, nine assertions passed using Firestore emulator 1.22.0 and Node server SDK 7.11.6 on loopback with a <code>demo-</code> project. They checked both documents before deletion; the absent parent and retained child afterward; an empty parent query; the child query and collection-group result; explicit child cleanup; and a second parent delete leaving it absent.</p>
<p>This is a synthetic test of document/path semantics. It does not test production IAM, billing, client Security Rules, a recursive cleanup service, concurrent cleanup, or Cengal's UI. Server SDK access does not validate mobile/web Security Rules. Different deletion tools can deliberately implement recursion, so check the exact operation you used rather than generalizing from its button label.</p>
<h2>One question for your data model</h2>
<p>When a parent record is removed, which child records should survive—and where is that retention decision enforced?</p>
]]></content:encoded></item><item><title><![CDATA[Changed one Firestore setting and lost its neighbours? Check the field path]]></title><description><![CDATA[Reproduce parent-map replacement versus leaf-field updates with synthetic Firestore data, literal dotted names and clear concurrency limits.]]></description><link>https://cengal.hashnode.dev/changed-one-firestore-setting-and-lost-its-neighbours-check-the-field-path</link><guid isPermaLink="true">https://cengal.hashnode.dev/changed-one-firestore-setting-and-lost-its-neighbours-check-the-field-path</guid><category><![CDATA[firestore]]></category><dc:creator><![CDATA[Cengal]]></dc:creator><pubDate>Wed, 21 Oct 2026 02:34:06 GMT</pubDate><content:encoded><![CDATA[<p>Originally published at <a href="https://cengal.app/guides/firestore-nested-map-field-path/">cengal.app</a>.</p>
<p>Changing <code>theme</code> should not require rebuilding an entire preferences map. But the shape of a Firestore update determines whether you replace that map or update one leaf inside it.</p>
<p>Disclosure: This is an AI-assisted technical article prepared for Cengal, a Firestore developer tool. The examples and original code-generated illustrations use synthetic data, not customer records. You do not need Cengal or a paid product to use the solution.</p>
<h2>A real report, not a newly discovered incident</h2>
<p>On March 7, 2018, a developer <a href="https://stackoverflow.com/questions/49150917/update-fields-in-nested-objects-in-firestore-documents">reported on Stack Overflow</a> that updating a map's <code>test</code> field also removed its sibling <code>test2</code>. Their shown update supplied the parent field with a new object.</p>
<p>That code is consistent with the map-replacement behavior described in <a href="https://firebase.google.com/docs/firestore/manage-data/add-data#update_fields_in_nested_objects">Firebase's nested-field guidance</a>. This does not establish the complete cause of that developer's environment, nor show how often the issue occurs today. It gives us a concrete behavior to reproduce rather than a reason to blame a console, cache or SDK bug.</p>
<h2>Reproduce the distinction with one disposable document</h2>
<p>The following is Node.js <strong>server SDK</strong> code using <code>@google-cloud/firestore</code>, not the modular Web SDK or Flutter API. Run it against a local emulator, never a real collection you care about. Install the SDK in your own scratch project and start a Firestore emulator on <code>127.0.0.1:8080</code> with project ID <code>demo-map-example</code> first. Server libraries do not exercise client Security Rules.</p>
<pre><code class="language-js">const { Firestore } = require('@google-cloud/firestore');

// Fail closed rather than accidentally using production credentials.
if (process.env.FIRESTORE_EMULATOR_HOST !== '127.0.0.1:8080') {
  throw new Error('Start the local emulator and set FIRESTORE_EMULATOR_HOST');
}
const db = new Firestore({ projectId: 'demo-map-example' });
const ref = db.collection('profiles').doc('demo');
const seed = {
  name: 'Ada',
  preferences: { theme: 'dark', notifications: true }
};

async function run() {
  await ref.set(seed);
  await ref.update({ preferences: { theme: 'light' } });
  console.log((await ref.get()).data());
  // { name: 'Ada', preferences: { theme: 'light' } }

  await ref.set(seed); // reset only this disposable fixture
  await ref.update({ 'preferences.theme': 'light' });
  console.log((await ref.get()).data());
  // { name: 'Ada', preferences: { theme: 'light', notifications: true } }
}
run()
  .catch(error =&gt; {
    console.error(error);
    process.exitCode = 1;
  })
  .finally(() =&gt; db.terminate());
</code></pre>
<p>Set <code>FIRESTORE_EMULATOR_HOST=127.0.0.1:8080</code> in that scratch project's shell before running. If your emulator uses another port, change both the environment value and the guard. The <code>demo-</code> project name and explicit local-host guard are deliberate safety boundaries.</p>
<h2>The update key defines the boundary</h2>
<p><img src="https://cengal.app/images/guides/map-boundary.png" alt="Diagram comparing a parent preferences replacement, which removes notifications, with a preferences.theme leaf update, which preserves notifications." /></p>
<p><em>Original AI-assisted, code-generated diagram for this article. Both branches start from the same synthetic document.</em></p>
<p><code>preferences</code> targets the map value. The supplied <code>{ theme: 'light' }</code> becomes that value; omitted map siblings do not survive. The unrelated top-level <code>name</code> still survives because this is an update of <code>preferences</code>, not a replacement of the whole document.</p>
<p><code>'preferences.theme'</code> targets one nested field. Firestore changes that leaf without replacing the surrounding map. Quotes matter here: this is a string field path in the update object.</p>
<p><img src="https://cengal.app/images/guides/map-reaction.png" alt="A cartoon notifications field with a suitcase says: I was not in your replacement map. The caption contrasts replacing the map with updating a leaf." /></p>
<p><em>Original AI-assisted, code-generated humor illustration; no third-party clips or likenesses. This static equivalent preserves the joke without uncontrolled motion. The <a href="https://cengal.app/guides/firestore-nested-map-field-path/#the-update-key-defines-the-boundary">original article</a> has the animation with reduced-motion fallback and Pause/Resume controls.</em></p>
<h2>What if a field name itself contains a dot?</h2>
<p>A literal field name <code>a.b</code> is not the same as a nested <code>a</code> map containing <code>b</code>. For the Node server SDK, pass separate literal segments to <a href="https://docs.cloud.google.com/nodejs/docs/reference/firestore/latest/firestore/fieldpath"><code>FieldPath</code></a>, using the alternating field-path/value update overload:</p>
<p>Inside an async function using the existing <code>ref</code>:</p>
<pre><code class="language-js">const { FieldPath } = require('@google-cloud/firestore');
await ref.update(new FieldPath('preferences', 'a.b'), true);
// preferences now includes the literal key "a.b": true

await ref.update({ 'preferences.a.b': true });
// This instead targets preferences -&gt; a -&gt; b.
</code></pre>
<p>Do not put a <code>FieldPath</code> object into a computed JavaScript object key; that coerces it to a string. Do not concatenate arbitrary field names into dotted paths if those names may contain dots. Use the SDK's field-path API appropriate to your platform.</p>
<h2>A small patch is not a concurrency strategy</h2>
<p>Suppose another writer turns notifications off. A subsequent theme-only leaf update leaves that newer notifications value alone. A stale whole-map update can put the old notifications value back.</p>
<p>That does not make leaf updates conflict-proof. Two writers setting the same leaf can still overwrite one another. If a write must depend on values you read, evaluate a <a href="https://firebase.google.com/docs/firestore/manage-data/transactions">Firestore transaction</a> and its retry behavior. A field path is not undo, a backup, a restore mechanism or an array-index editing API.</p>
<p>Also, <a href="https://docs.cloud.google.com/nodejs/docs/reference/firestore/latest/firestore/documentreference"><code>update()</code> requires an existing document</a>. Do not silently switch to a create-capable write just to make a missing-document error disappear; first decide whether creation is actually intended.</p>
<h2>What was tested—and what was not</h2>
<p>On October 21, 2026, the accompanying local test used Firestore emulator 1.22.0 and Node server SDK 7.11.6 with a <code>demo-</code> project on loopback. Seven assertions passed: parent-map replacement; leaf-path preservation; literal dotted names; nested dotted paths; rejection of an update to a missing document; preservation of a newer sibling by a leaf patch; and overwriting of that sibling by a stale whole map.</p>
<p>Each case reset synthetic data. The two-writer cases used deterministic sequential interleavings, not a concurrency stress test. These results validate the example's map/path semantics, not production IAM, client Rules, indexes, billing, or Cengal's editor. A full-document JSON save in any tool is not automatically equivalent to a leaf-path SDK update.</p>
<h2>Before changing a real document</h2>
<ol>
<li>Identify the exact field you intend to change, and whether any segment contains a literal dot.</li>
<li>Reproduce the update shape with disposable emulator data.</li>
<li>Read the result back and check the fields you expected to remain.</li>
<li>Handle missing documents and read-dependent writes deliberately. Apply your normal access controls and recovery practices before production changes.</li>
</ol>
<p>Which boundary has caused more surprises in your application: replacing a nested map, handling literal dotted field names, or coordinating two writers? A small anonymized before/update/after example makes the distinction easier to discuss.</p>
]]></content:encoded></item><item><title><![CDATA[You Added orderBy. Where Did Your Firestore Documents Go?]]></title><description><![CDATA[A Firestore sort can exclude documents missing the ordered field. Reproduce it with three sample tasks before changing production data.]]></description><link>https://cengal.hashnode.dev/you-added-orderby-where-did-your-firestore-documents-go</link><guid isPermaLink="true">https://cengal.hashnode.dev/you-added-orderby-where-did-your-firestore-documents-go</guid><dc:creator><![CDATA[Cengal]]></dc:creator><pubDate>Tue, 20 Oct 2026 04:48:24 GMT</pubDate><content:encoded><![CDATA[<p>If you add a sort to a Firestore query and a document disappears, the first thing to check is surprisingly simple: does that document contain the field you're sorting by?</p>
<p>Examples use synthetic data. AI assistance was used in preparation; verification scope is stated below. Originally published on <a href="https://cengal.app/guides/firestore-orderby-missing-documents/">Cengal</a>.</p>
<p><code>orderBy()</code> doesn't only arrange the results. It also excludes documents that don't have the ordered field. Firebase calls this out in its <a href="https://firebase.google.com/docs/firestore/query-data/order-limit-data">ordering documentation</a>, but it's an easy detail to miss when the same collection looked fine a moment earlier.</p>
<p>The document hasn't necessarily been deleted. It may no longer qualify for that particular query.</p>
<p><img src="https://cengal.app/images/guides/firestore-orderby-missing-field.gif" alt="Before sorting, the open-task query returns triage and review. After ordering by priority, review is excluded because its priority field is missing." /></p>
<p>The sort didn't delete the task—it stopped inviting it. Original six-second sample-data GIF, created with AI assistance, not an app screenshot. The still tells the same story.</p>
<p><img src="https://cengal.app/images/guides/firestore-orderby-missing-field.svg" alt="Before sorting, the open-task query returns triage and review. After ordering by priority, review is excluded because its priority field is missing." /></p>
<p>Original Cengal sample-data still illustration, created with AI assistance, not an app screenshot.</p>
<h2>A small example you can reason about</h2>
<p>Imagine a task collection with these three synthetic documents:</p>
<table>
<thead>
<tr>
<th>Document ID</th>
<th>status</th>
<th>priority</th>
</tr>
</thead>
<tbody><tr>
<td><code>triage</code></td>
<td><code>"open"</code></td>
<td>number <code>1</code></td>
</tr>
<tr>
<td><code>review</code></td>
<td><code>"open"</code></td>
<td>field missing</td>
</tr>
<tr>
<td><code>archive</code></td>
<td><code>"done"</code></td>
<td>number <code>2</code></td>
</tr>
</tbody></table>
<p>Start with an open-task query. This example uses the Node.js server SDK; don't mix its syntax with the Web SDK's modular API.</p>
<pre><code class="language-js">const tasks = db.collection("orderbyExampleTasks");
const openTasks = tasks.where("status", "==", "open");

const before = await openTasks.get();
console.log(before.docs.map(doc =&gt; doc.id).sort());
// ["review", "triage"]
</code></pre>
<p>Now add the sort:</p>
<pre><code class="language-js">const after = await openTasks.orderBy("priority").get();
console.log(after.docs.map(doc =&gt; doc.id));
// ["triage"]
</code></pre>
<p><code>review</code> still has <code>status: "open"</code>, but it has no <code>priority</code> field. Adding the sort excludes it. A larger limit won't bring it back, because the missing field—not the page size—is the issue.</p>
<p><img src="https://cengal.app/images/guides/firestore-query-guest-list-reaction.gif" alt="An original cartoon document named review looks unimpressed at a query door asking for priority, then reminds us it still exists." /></p>
<p>review at the query door: “I’m still in the database, you know.” Original reaction GIF, created with AI assistance, not an app screenshot.</p>
<p>We ran these examples against synthetic data in the local Standard-edition Firestore emulator. The baseline returned all three IDs, the open-status query returned <code>review</code> and <code>triage</code>, and the ordered open-status query returned only <code>triage</code>, both with and without a limit of two. The runnable fixture and exact environment are recorded in our publishing evidence. This checks query behavior, not production index requirements, permissions, billing, or an application UI.</p>
<h2>Check the document, not just the table</h2>
<p>Remove the ordering temporarily and run a small, bounded read against the same project, database, and collection. If the document returns, inspect that record's fields directly.</p>
<p>Look for an absent key, a slightly different field name, or a value written somewhere else in a nested map. A column displayed for other rows isn't proof that every document has that field. And a blank table cell isn't enough to tell you whether a field is missing, null, or simply not shown by that view.</p>
<p>Keep errors visible during this check. A successful query with one result is different from a permission or missing-index error. Don't catch every exception and turn it into an empty array—that makes separate problems look alike.</p>
<h2>Decide what the field means before filling it in</h2>
<p>There are two reasonable questions to ask:</p>
<ul>
<li>Should every task have a priority? If so, check the writers that create tasks, then plan a reviewed correction for existing records.</li>
<li>Is priority genuinely optional? If so, an ordered query over that field may not be the right way to fetch every open task.</li>
</ul>
<p>Don't assign a value to every production document just to make a screen look complete. A default priority changes the meaning of the data. Work out the intended behavior first, and test it with a small fixture.</p>
<p>If you change the query's filters or ordering, reset its pagination too. A cursor from the previous query is another reason a page can look incomplete. Firebase's <a href="https://firebase.google.com/docs/firestore/query-data/query-cursors">cursor guidance</a> explains how cursor values relate to the query's ordering.</p>
<h2>Where Cengal can help</h2>
<p>If you'd rather inspect the records visually, Cengal can browse Firestore collections, show document fields and types, and build bounded queries on Mac. You can compare the result with and without the sort, then inspect the document that drops out. Core browsing and querying are available on Free; macOS 14 or later is required.</p>
<p>If the data needs an edit, Cengal lets you stage it and <a href="https://cengal.app/docs/table-editing/">review the change before applying it</a>. That doesn't make a schema decision for you, fix Security Rules, create an index, or remove Firestore read costs. The useful part here is making the stored record and intended change easier to inspect.</p>
<p>For the related workflow, see <a href="https://cengal.app/docs/queries/">Cengal queries</a> and <a href="https://cengal.app/docs/firestore-composite-indexes/">missing composite-index errors</a>. The same diagnostic works in your SDK or Firebase Console; you don't need a particular GUI to use it.</p>
]]></content:encoded></item><item><title><![CDATA[A Firestore query returns zero documents: debug it with a four-document fixture]]></title><description><![CDATA[Debug empty Firestore queries with a four-document fixture and 13 emulator checks covering types, missing fields, ordering, arrays, and pagination.]]></description><link>https://cengal.hashnode.dev/a-firestore-query-returns-zero-documents-debug-it-with-a-four-document-fixture</link><guid isPermaLink="true">https://cengal.hashnode.dev/a-firestore-query-returns-zero-documents-debug-it-with-a-four-document-fixture</guid><category><![CDATA[Firebase]]></category><category><![CDATA[firestore]]></category><dc:creator><![CDATA[Cengal]]></dc:creator><pubDate>Tue, 20 Oct 2026 03:33:53 GMT</pubDate><content:encoded><![CDATA[<p>An empty query result and a failed query are different problems. Before changing indexes, permissions, or data, establish which one you have.</p>
<p>This checklist covers Firestore’s Core query API, with examples verified in a local Standard-edition emulator. It does not cover MongoDB-compatible queries or Firestore Pipeline operations.</p>
<p>Disclosure: This article was prepared for Cengal, a native Mac workspace for Firestore. AI assistance was used in drafting and automated verification. The sample cases were executed against synthetic emulator data, not a customer database or a live Cengal session.</p>
<h2>1. Keep errors separate from empty results</h2>
<p>Avoid turning every exception into an empty array:</p>
<pre><code class="language-js">try {
  const snapshot = await query.get();
  console.log({ empty: snapshot.empty, size: snapshot.size });
} catch (error) {
  console.error(error.code, error.message);
  throw error;
}
</code></pre>
<p>This example uses the Node.js server SDK. Adapt the syntax to your client SDK rather than mixing APIs.</p>
<p>A permissions error is not evidence that no matching documents exist. Likewise, an index error should remain visible. Diagnose the actual error before rewriting the query.</p>
<p>For mobile and web clients, Security Rules are not a post-query filter. A query that could return unauthorized documents can be rejected as a whole. Do not make the database public to investigate an empty screen. See Firebase’s <a href="https://firebase.google.com/docs/firestore/security/rules-query">secure-query guidance</a>.</p>
<h2>2. Establish the exact target</h2>
<p>Write down:</p>
<ul>
<li>Project ID and database ID.</li>
<li>Emulator versus deployed database.</li>
<li>Full collection path, including parent document IDs.</li>
<li>Collection query versus collection-group query.</li>
<li>The identity and SDK used for the read.</li>
</ul>
<p>A query against <code>orders</code> does not search every nested collection named <code>orders</code>. A different database or environment can also contain a perfectly valid—but empty—collection.</p>
<p>Use a bounded baseline read on the intended collection. If that returns nothing, investigate the target before adding filters. Avoid downloading an entire production collection just to debug a predicate.</p>
<h2>3. Use a tiny fixture to test assumptions</h2>
<p>Here is the complete synthetic dataset used for the checks below:</p>
<pre><code class="language-js">const rows = {
  a: {
    status: "active",
    score: 10,
    tags: ["red"],
    optional: null
  },
  b: {
    status: "active",
    score: "10",
    tags: ["blue"]
  },
  c: {
    status: "Active",
    score: 20,
    tags: "red"
  },
  d: {
    status: "archived",
    score: 30,
    tags: ["red"],
    optional: "present"
  }
};
</code></pre>
<p>Each key is a document ID in a collection called <code>queryChecklist</code>. Notice the intentional inconsistencies: capitalization, a numeric string, a scalar where other documents have arrays, and missing fields.</p>
<p>With a Node server-SDK collection reference named <code>c</code>, these were the observed results:</p>
<table>
<thead>
<tr>
<th>Query</th>
<th>Matching document IDs</th>
</tr>
</thead>
<tbody><tr>
<td><code>c.get()</code></td>
<td>a, b, c, d</td>
</tr>
<tr>
<td><code>c.where("status", "==", "active")</code></td>
<td>a, b</td>
</tr>
<tr>
<td><code>c.where("status", "==", "ACTIVE")</code></td>
<td>none</td>
</tr>
<tr>
<td><code>c.where("score", "==", 10)</code></td>
<td>a</td>
</tr>
<tr>
<td><code>c.where("score", "==", "10")</code></td>
<td>b</td>
</tr>
<tr>
<td><code>c.where("status", "==", "active").where("status", "==", "archived")</code></td>
<td>none</td>
</tr>
<tr>
<td><code>c.where("status", "==", "active").orderBy("optional")</code></td>
<td>a</td>
</tr>
<tr>
<td><code>c.where("tags", "array-contains", "red")</code></td>
<td>a, d</td>
</tr>
<tr>
<td><code>c.where("tags", "==", "red")</code></td>
<td>c</td>
</tr>
<tr>
<td><code>c.where("optional", "==", null)</code></td>
<td>a</td>
</tr>
<tr>
<td><code>c.where("optional", "not-in", ["absent"])</code></td>
<td>d</td>
</tr>
<tr>
<td><code>c.where("optional", "not-in", [null, "absent"])</code></td>
<td>none</td>
</tr>
<tr>
<td><code>c.orderBy("score").startAfter("zzz")</code></td>
<td>none</td>
</tr>
</tbody></table>
<p>Call <code>.get()</code> on each query to execute it. Result IDs in the table are sorted for comparison; the table is not promising a particular query result order.</p>
<p>The tests used Firestore emulator 1.22.0 in Standard edition with <code>@google-cloud/firestore</code> 7.11.6. These are query-semantics checks—not production index, authorization, billing, or application-UI tests. Server SDK access also does not reproduce mobile/web Security Rules behavior.</p>
<p><img src="https://cdn.hashnode.com/uploads/covers/6ad6d7797978c30ed50af693/e4e9446d-ea8c-45d5-9020-0fb9a0ad6638.gif" alt="Animation: the active-status filter includes documents a and b; ordering by optional keeps a with null but excludes b with a missing field. Both documents remain stored." /></p>
<p><em>Original Cengal animation, created with AI assistance from the synthetic fixture above—not an app recording. Document b has not vanished; it is just off this query’s guest list. The static illustration in section 5 provides a non-animated explanation.</em></p>
<h2>4. Add constraints one at a time</h2>
<p>Start with the baseline. Then restore one filter, run it, and record the result before restoring the next.</p>
<p>The conflicting-status example is deliberately simple: both predicates can work separately, but no document can have both scalar values at once.</p>
<p>Check the actual stored value and type. In this fixture, <code>10</code> and <code>"10"</code> produce different matches. So do <code>"active"</code> and <code>"ACTIVE"</code>. A UI rendering both values similarly does not make them interchangeable.</p>
<p>For arrays, distinguish “the field contains this element” from “the whole field equals this value.” Our <code>tags</code> examples return different documents for precisely that reason.</p>
<p>Firebase documents the relevant operators in its <a href="https://firebase.google.com/docs/firestore/query-data/queries">query reference</a>.</p>
<h2>5. Remove ordering while diagnosing missing documents</h2>
<p>The active-status filter returns <code>a</code> and <code>b</code>. Adding <code>orderBy("optional")</code> leaves only <code>a</code>, because <code>b</code> has no <code>optional</code> field.</p>
<p>Sorting can therefore change which documents qualify—not merely their display order. Firebase explicitly documents that ordering excludes documents missing the ordered field in <a href="https://firebase.google.com/docs/firestore/query-data/order-limit-data">order and limit data</a>.</p>
<p>Do not immediately backfill a production field. First confirm whether the field is genuinely required by your data model and query.</p>
<p><img src="https://cengal.app/images/guides/firestore-orderby-missing-field.svg" alt="Adding orderBy priority excludes the review document because its priority field is missing, while the document remains in Firestore." /></p>
<p><em>Original Cengal sample-data illustration, not an app screenshot. This separate open-status/priority example illustrates the same missing-field behavior. Source: <a href="https://cengal.app/guides/firestore-orderby-missing-documents/">Cengal’s ordering guide</a>.</em></p>
<h2>6. Treat missing fields and null as different states</h2>
<p>Document <code>a</code> has <code>optional: null</code>. Documents <code>b</code> and <code>c</code> have no <code>optional</code> field.</p>
<p>In this fixture, equality with null returns only <code>a</code>. The negative-membership query does not act as “everything except this value, including missing fields.” Adding null to the <code>not-in</code> comparison list produces no matches.</p>
<p>Inspect a representative document rather than guessing how absent values are represented.</p>
<h2>7. Reset pagination before changing the data</h2>
<p>A cursor can legitimately describe a page after the final result.</p>
<p>Remove the cursor and use a small, positive limit while checking the first page. Rebuild pagination state after changing filters or ordering. Confirm that cursor values correspond to the current ordering rather than a previous query.</p>
<p>The final fixture check intentionally places the cursor beyond the available ordered values. It returns an empty page even though the collection contains four documents.</p>
<h2>A practical stopping point</h2>
<p>Before changing anything, you should be able to answer:</p>
<ol>
<li>Did the request succeed?</li>
<li>Is this the intended project, database, and collection?</li>
<li>Does a bounded baseline read return documents?</li>
<li>Which single constraint first removes the expected document?</li>
<li>Are field types, missing values, ordering, and pagination consistent with that constraint?</li>
</ol>
<p>That is a useful debugging result even before a fix: it turns “Firestore is empty” into a specific, reproducible explanation.</p>
<p>If you prefer inspecting documents visually, <a href="https://cengal.app">Cengal</a> is one option for browsing and querying Firestore on Mac, requiring macOS 14 or later. The same checklist works with your SDK or Firebase console; no particular GUI is required.</p>
]]></content:encoded></item></channel></rss>