Chapter 02 · Documents, Collections, Fields, References, Maps, Arrays, Timestamps, GeoPoints, and Limits

Supported Native Data Types, Null, Numeric Ordering, Timestamps, References, GeoPoints, Bytes, Arrays, and Maps

Predict Firestore value semantics across SDKs, including mixed-type ordering, null versus missing, timestamp precision, numeric boundaries, references, GeoPoints, bytes, arrays, maps, and edition-specific differences.

Beginner → Advanced90–120 minutesAtlasMart emulator-first labFirebase CLI 15.30.0 · Web SDK 12.19.0 · Admin Node 14.4.0 · Node.js 22+Firestore Standard Native semantics unless explicitly labeled EnterpriseLast reviewed: September 2026

Learning outcomes

AtlasMart's path design is useful only if every service agrees on what each field means. Firestore values are richer than JSON: timestamps, references, GeoPoints, bytes, arrays, maps, vectors, integers and doubles have database semantics that do not map perfectly to every host language. A schema that says only “price is a number” or “createdAt is a date” is too vague for cross-platform correctness.

01

Name Firestore Native value types and predict deterministic ordering when a field contains mixed types.

02

Distinguish a missing field from an explicit null value and understand the resulting query/index implications.

03

Explain integer/double interleaving, JavaScript safe-integer limits, timestamp microsecond precision, and NaN ordering.

04

Use DocumentReference, GeoPoint, bytes, arrays, and maps without treating SDK wrapper objects as plain JSON.

05

Round-trip typed AtlasMart fixtures through Admin and Web SDKs and compare canonical semantic values.

Chapter 02 baseline reviewed 14 September 2026

The lab continues Chapter 01 exactly: project demo-atlasmart-firestore; Firestore emulator 127.0.0.1:8080; Auth emulator 127.0.0.1:9099; Emulator UI 127.0.0.1:4000; Firebase CLI 15.30.0; Firebase JavaScript SDK 12.19.0; Firebase Admin Node.js SDK 14.4.0; Node.js 22 or newer. Standard Native semantics are the default. Enterprise-only differences are labeled explicitly.

Execution and evidence note

These lessons are authored against current official documentation and the deterministic emulator design from Chapter 01. This generation environment does not run the Firebase Emulator Suite or install the pinned npm dependencies, so command output is described by invariant and expected shape rather than presented as captured execution. The emulator is evidence for local application behavior, not proof of production quotas, regional latency, backend index topology, or billing.

1. Firestore values have database types, not just JSON shapes

Native mode supports null, booleans, integers, floating-point values, timestamps/date-time values, strings, bytes, document references, GeoPoints, arrays, vector embeddings, and maps. The database stores type information; a string "42" is not the same value as integer 42, and a plain string containing products/p-1001 is not the same value as a DocumentReference to that path.

Type AtlasMart example Important boundary
Null middleName: null Explicit value; different from a missing field.
Integer / double stock: 12, price: 129.9 Firestore supports signed 64-bit integers and double precision; host-language precision can be narrower.
Timestamp createdAt Stored with microsecond precision; finer input precision is rounded down.
Reference productRef Typed document path; not a join or existence constraint.
GeoPoint warehouse latitude/longitude Ordered by latitude then longitude; distance search needs geospatial design.
Bytes small fingerprint/blob Binary value; Standard query comparison considers indexed-prefix constraints.
Array tags, bounded variants Order preserved; Standard does not permit arrays nested directly inside arrays.
Map dimensions, attributes Keys are stored in sorted key order; indexed subfields can multiply index entries.
Vector embedding field Specialized type used later; do not substitute a plain numeric array when vector search semantics are required.

2. Mixed-type ordering is deterministic—but mixed schemas are still dangerous

If documents in one queryable collection place different Firestore types in the same field, Firestore has a deterministic cross-type order. The current documented order is null, boolean, numeric, date/time, string, bytes, reference, GeoPoint, array, vector, then map. Integers and doubles are interleaved by numeric value rather than grouped by storage type.

That determinism does not make a mixed-type schema desirable. If price is numeric in most products but a string in imported legacy products, an ordered query can still return a deterministic result that violates business meaning. AtlasMart should treat type drift as a schema-contract violation and detect it in fixtures/migration tests rather than relying on database ordering to hide it.

NaN is a boundary case

Firestore normalizes NaN values and orders NaN below negative infinity in its numeric total ordering. Do not use NaN as a business sentinel for missing price; model absence explicitly.

3. Missing is not null; JavaScript numbers are not arbitrary int64 containers

A missing field has no value. An explicit null is a stored value with its own type and order. This distinction becomes important in queries, rules, migrations, partial updates, and client rendering. A field that is optional should have a documented contract: either absent means “unknown,” null means “intentionally empty,” or the schema forbids one of those states.

Firestore's integer type is signed 64-bit, but JavaScript's ordinary Number cannot exactly represent every 64-bit integer. That means an upstream order number or external account ID that happens to be numeric must not automatically be modeled as a Firestore integer if exact round-trip through JavaScript matters. Identifiers are usually safer as strings; arithmetic quantities should stay numeric with explicit range tests.

precision sanity check · Node.js host language
const safe = Number.MAX_SAFE_INTEGER;console.log(safe);       // 9007199254740991console.log(safe + 1);   // representableconsole.log(safe + 2);   // may collapse to same Number representation// Rule: do not model opaque external 64-bit identifiers as JS Number.// Store them as strings when exact textual identity matters.

4. Timestamps, references, GeoPoints, bytes, arrays, and maps need explicit canonicalization

Firestore timestamps are not ordinary JSON strings. When stored, their precision is microseconds; finer precision is rounded down. Document references contain database/path identity. GeoPoints preserve latitude/longitude semantics. Byte values are binary, and SDKs expose them as platform-specific wrappers such as Web Bytes or server-side buffers. Maps and arrays preserve nested typed values rather than becoming JSON by magic.

For logs, API responses, or cross-SDK comparison, build an explicit canonical encoder instead of calling JSON.stringify() and hoping every wrapper serializes identically. Canonicalization should preserve a type tag where ambiguity matters—for example, {type:"timestamp", micros:...} or {type:"reference", path:"products/p-1001"}.

canonical semantic view · browser/Web SDK
import { Timestamp, GeoPoint, Bytes, DocumentReference } from "firebase/firestore";function canonical(value) {  if (value instanceof Timestamp) {    return { type: "timestamp", seconds: value.seconds, nanoseconds: value.nanoseconds };  }  if (value instanceof GeoPoint) {    return { type: "geopoint", latitude: value.latitude, longitude: value.longitude };  }  if (value instanceof Bytes) {    return { type: "bytes", base64: value.toBase64() };  }  if (value instanceof DocumentReference) {    return { type: "reference", path: value.path };  }  if (Array.isArray(value)) return value.map(canonical);  if (value && typeof value === "object") {    return Object.fromEntries(Object.entries(value).map(([k,v]) => [k, canonical(v)]));  }  return value;}

5. Edition differences matter even at the value layer

Do not assume Standard and Enterprise Native behavior is identical. Current documentation explicitly distinguishes, among other things, nested-array support and the size/query treatment of long string/byte values. Standard Native disallows an array value directly nested as an element of another array; Enterprise Native permits it. Standard also applies the familiar value-size/indexed-prefix constraints to strings and bytes, while Enterprise documentation describes different value-size behavior bounded by document/index-entry limits.

The course therefore records edition next to every fixture that exercises an edition-sensitive boundary. A successful Enterprise write is not evidence that the same shape is valid in Standard, and emulator support must not be used to erase the production edition contract.

Course default

Chapter 02 labs use Standard Native semantics unless a block is explicitly labeled Enterprise. This keeps the mandatory path aligned with the broad Firebase mobile/web model and avoids making Enterprise billing a prerequisite.

6. Hands-on lab: round-trip a typed product through Admin and Web

The trusted script creates one document containing representative values. The Web client reads the same document from the emulator and canonicalizes wrapper types. The goal is not to prove every mobile SDK implementation; it is to establish a semantic fixture that later Android/Apple tests must match.

scripts/ch02-types-admin.mjs · trusted writer
process.env.FIRESTORE_EMULATOR_HOST = "127.0.0.1:8080";process.env.GCLOUD_PROJECT = "demo-atlasmart-firestore";import { initializeApp } from "firebase-admin/app";import { getFirestore, Timestamp, GeoPoint } from "firebase-admin/firestore";initializeApp({ projectId: "demo-atlasmart-firestore" });const db = getFirestore();const ref = db.doc("products/p-types-001");await ref.set({  sku: "CAM-4K-001",  discontinuedReason: null,  stock: 12,  price: 129.90,  updatedAt: Timestamp.fromDate(new Date("2026-09-14T10:11:12.123456Z")),  warehouse: new GeoPoint(47.3769, 8.5417),  checksum: Buffer.from([0xde, 0xad, 0xbe, 0xef]),  categoryRef: db.doc("categories/cameras"),  tags: ["outdoor", "camera"],  attributes: { resolution: "4K", weatherproof: true },  schemaVersion: 2});console.log("wrote", ref.path);
scripts/ch02-types-web.mjs · untrusted reader on emulator
process.env.FIRESTORE_EMULATOR_HOST = "127.0.0.1:8080";import { initializeApp } from "firebase/app";import { getFirestore, connectFirestoreEmulator, doc, getDoc, Timestamp, GeoPoint, Bytes, DocumentReference } from "firebase/firestore";const app = initializeApp({ apiKey: "demo-key", projectId: "demo-atlasmart-firestore", appId: "1:123:web:demo" });const db = getFirestore(app);connectFirestoreEmulator(db, "127.0.0.1", 8080);const snap = await getDoc(doc(db, "products", "p-types-001"));const data = snap.data();console.assert(snap.exists());console.assert(data.updatedAt instanceof Timestamp);console.assert(data.warehouse instanceof GeoPoint);console.assert(data.checksum instanceof Bytes);console.assert(data.categoryRef instanceof DocumentReference);console.assert(data.discontinuedReason === null);console.assert(!Object.hasOwn(data, "fieldThatWasNeverWritten"));console.log("typed read OK", snap.ref.path);

Expected invariant: the same stored document is exposed through SDK-native wrapper types and null/missing remain distinguishable. Do not compare class names across languages; compare semantic values and type categories.

7. Deliberately wrong approach: stringify every special value

It is tempting to store timestamps as ISO strings, references as path strings, coordinates as "lat,lng", and bytes as hex simply because those values are easy to print. Sometimes that is correct for an external API contract, but doing it indiscriminately throws away native ordering/query semantics and makes every client reconstruct types manually.

The repair is to separate the database schema from transport/logging representations. Store native Firestore types when their semantics are useful. At API/log boundaries, canonicalize explicitly. For opaque identifiers, keep strings. For money, define units/rounding deliberately rather than assuming binary floating point is an accounting type.

Production judgment

Cross-SDK portability is a schema property. Every field contract should state semantic type, optional/null policy, precision/range, client writability, index intent, and conversion rules—not only an example JSON value.

Knowledge check

Check your understanding

  1. What is the difference between a missing field and a field containing null?
  2. How are Firestore integers and doubles ordered relative to each other?
  3. Why can a signed 64-bit Firestore integer still be unsafe for a JavaScript identifier?
  4. What precision does Firestore retain for timestamps?
  5. Does a DocumentReference automatically fetch or validate its target?
Review the answers

1. Missing means the field has no stored value; null is an explicit Firestore value with its own type and ordering. Schemas and queries should define which state is valid.

2. They are interleaved in numeric order rather than grouped by numeric storage type.

3. JavaScript Number cannot exactly represent every signed 64-bit integer. Opaque exact IDs should commonly be strings.

4. Current documentation states microsecond precision; additional precision is rounded down when stored.

5. No. It is a typed path value. Reads, existence checks, authorization, and lifecycle remain explicit.

Summary and next step

Firestore values carry database semantics that must survive SDK boundaries. AtlasMart now distinguishes null from missing, numeric value from opaque ID, timestamp from string, reference from path text, and native bytes/GeoPoints/maps/arrays from JSON approximations. Next we put those values under the service's hard document/path/nesting/index limits and turn them into enforceable schema governance.

Next: Document Size, Field/Path/Nesting Constraints, Index Entry Implications, and Schema Governance.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.