agentleFS
DentVegaDentVega/firebase-agent-skills/skills/firebase-firestore/SKILL.md

firebase-firestore

Skill0 starsChanged 5 months agoMITView on GitHub

Provisions and uses Cloud Firestore — creating databases, designing collections and documents, writing security rules, defining composite indexes, and writing client SDK reads/writes. Use whenever the user needs persistent structured storage that syncs in real time across clients.

  • Reads credentials

5 other projects use a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

7 rules. 0 are common, 3 are distinctive, and 4 are very specific to this project.

  • 0

    Common

    Most of the 5 edited versions we read keep these.

  • 3

    Distinctive

    This author's own choices. Worth a look.

  • 4

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    If none exists, create one.

    line 43
  2. Distinctive:

    Read what you display: If you list 50 todos, store enough on each todo to render the list without N+1 fetches.

    line 59
  3. Distinctive:

    Top-level collections for cross-cutting queries: If you ever need "all posts site-wide, newest first", use a top-level collection.

    line 61
  4. Very specific:

    Default to deny everything, then open up the minimum needed.

    line 69
  5. Very specific:

    CRITICAL: never write a rule like allow read, write: if request.auth != null.

    line 109
  6. Very specific:

    Polling with getDocs for UI that should react to changes: Use onSnapshot and return its unsubscribe from useEffect.

    line 155
  7. Very specific:

    Trusting client timestamps: Use serverTimestamp() on write and request.time in rules — never Date.now().

    line 156

“Common” is measured: kept by at least 6 in 10 of the 5 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Cloud Firestore

Minimum viable example

import firestore from "@react-native-firebase/firestore";

// Write
await firestore().collection("posts").add({
  title: "hi",
  authorId: uid,
  createdAt: firestore.FieldValue.serverTimestamp(),
});

// Real-time read
const unsub = firestore()
  .collection("posts")
  .where("authorId", "==", uid)
  .orderBy("createdAt", "desc")
  .onSnapshot((snap) => setPosts(snap.docs.map((d) => ({ id: d.id, ...d.data() }))));

That's the canonical pattern. serverTimestamp() instead of client time, onSnapshot instead of polling, return the unsubscribe.

1. Identify or create the database

List existing databases:

npx -y firebase-tools@latest firestore:databases:list

If none exists, create one. Default to Enterprise (native mode) unless the user has a specific reason to use Standard edition:

npx -y firebase-tools@latest firestore:locations
npx -y firebase-tools@latest firestore:databases:create "(default)" \
  --edition="enterprise" --location="<region>"

Pick the same region as the rest of the user's Firebase resources (Functions, Storage) to avoid cross-region latency and egress charges.

2. Data modeling

Firestore is document-oriented with collections of documents. Documents can hold subcollections. The right shape depends on access patterns, not on what feels normalized.

Rules of thumb

  • Read what you display. If you list 50 todos, store enough on each todo to render the list without N+1 fetches. Denormalize liberally.
  • Subcollections for ownership. users/{uid}/posts/{postId} is better than a top-level posts filtered by ownerId if posts are only ever shown in that user's context — rules become trivial.
  • Top-level collections for cross-cutting queries. If you ever need "all posts site-wide, newest first", use a top-level collection.
  • Avoid documents that grow unboundedly. A document max is 1 MiB. A chat with 100k messages must be a subcollection, not an array.
  • Use auto-generated IDs unless you have a strong external key (e.g. an order number).

See references/data-modeling.md for full patterns.

3. Security rules

Default to deny everything, then open up the minimum needed. Edit firestore.rules:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    match /users/{userId} {
      allow read, update, delete: if request.auth != null && request.auth.uid == userId;
      allow create: if request.auth != null && request.auth.uid == userId;

      match /posts/{postId} {
        allow read, write: if request.auth != null && request.auth.uid == userId;
      }
    }

    match /publicPosts/{postId} {
      allow read: if true;
      allow create: if request.auth != null
                    && request.resource.data.authorId == request.auth.uid;
      allow update, delete: if request.auth != null
                            && resource.data.authorId == request.auth.uid;
    }
  }
}

For a default-deny starter that follows this pattern, invoke the scaffold:

node node_modules/firebase-agent-skills/scripts/scaffold/firestore-rules.mjs \
  --out firestore.rules --collection todos

Deploy:

npx -y firebase-tools@latest deploy --only firestore:rules

CRITICAL: never write a rule like allow read, write: if request.auth != null; and ship it. It lets any signed-in user read or overwrite any document. Anchor every rule to ownership or membership.

See references/security-rules.md for advanced patterns: role-based access via custom claims, member-of-group checks, write validation, rate limiting.

4. Indexes

Single-field indexes are automatic. Composite indexes (more than one where + orderBy) must be declared in firestore.indexes.json:

{
  "indexes": [
    {
      "collectionGroup": "posts",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "authorId", "order": "ASCENDING" },
        { "fieldPath": "createdAt", "order": "DESCENDING" }
      ]
    }
  ]
}

Deploy:

npx -y firebase-tools@latest deploy --only firestore:indexes

The first time a query needs a missing composite index, Firestore throws an error with a console link that creates it for you. In dev, click the link; in CI / prod, commit the entry to firestore.indexes.json.

5. Client SDK usage

  • Web → references/web-sdk.md
  • Expo / React Native → references/react-native.md (use @react-native-firebase/firestore for native; the web SDK also works for managed Expo apps with reduced features)

Always-true patterns

  • Use onSnapshot for any UI that should react to changes. Polling with getDocs is almost always wrong.
  • Cleanup snapshot listeners in useEffect cleanup, otherwise they leak and the SDK keeps the document hydrated forever.
  • Batch writes ≤ 500 ops. For more, chunk and run them sequentially or in parallel batches.
  • Never put secrets in documents. Rules can be probed; if a field shouldn't be readable, don't store it client-side at all.

6. Common mistakes

  • allow read, write: if request.auth != null; — leaks every doc to every signed-in user. Always anchor rules to ownership (request.auth.uid == resource.data.ownerId) or membership.
  • Polling with getDocs for UI that should react to changes. Use onSnapshot and return its unsubscribe from useEffect. Otherwise listeners leak.
  • Trusting client timestamps. Use serverTimestamp() on write and request.time in rules — never Date.now().
  • Unbounded documents. 1 MiB hard cap. A chat with 100k messages must be a subcollection, not an array.
  • Querying object properties inside arrays. Firestore can only array-contains whole values. Restructure to a subcollection if you need to filter by inner fields.
  • Forgetting composite indexes in production. Dev gets a console link to create them; CI hits the missing-index error. Commit firestore.indexes.json.
  • Calling setDoc(ref, data, { merge: true }) and expecting create rules to fire. Merged writes evaluate update rules — write both.

7. Local emulator

For development, run the emulator suite so you don't burn quota and can iterate on rules safely:

npx -y firebase-tools@latest init emulators
npx -y firebase-tools@latest emulators:start --only firestore

Connect from the client:

import { connectFirestoreEmulator, getFirestore } from "firebase/firestore";
const db = getFirestore();
if (process.env.NODE_ENV === "development") {
  connectFirestoreEmulator(db, "127.0.0.1", 8080);
}

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.