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.
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.
Name Firestore Native value types and predict deterministic ordering when a field contains mixed types.
Distinguish a missing field from an explicit null value and understand the resulting query/index implications.
Explain integer/double interleaving, JavaScript safe-integer limits, timestamp microsecond precision, and NaN ordering.
Use DocumentReference, GeoPoint, bytes, arrays, and maps without treating SDK wrapper objects as plain JSON.
Round-trip typed AtlasMart fixtures through Admin and Web SDKs and compare canonical semantic values.
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.
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.
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.
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"}.
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.
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.
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);
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.
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
- What is the difference between a missing field and a field containing null?
- How are Firestore integers and doubles ordered relative to each other?
- Why can a signed 64-bit Firestore integer still be unsafe for a JavaScript identifier?
- What precision does Firestore retain for timestamps?
- 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
- Cloud Firestore data model — Official document/collection hierarchy and subcollection model.
- Choose a data structure — Official guidance for nested data, subcollections, and root-level collections.
- Supported data types — Current Native data types, sort ordering, precision, and edition-specific value behavior.
- Usage and limits — Current document, field, path, nesting, index-entry, and request limits.
- Index types in Cloud Firestore — Automatic/manual indexing, map/array indexing, index entries, and exemptions.
- Best practices for Cloud Firestore — Official document-ID, hotspot, location, and index-fan-out guidance.
- Add data to Cloud Firestore — Current SDK examples for IDs, timestamps, nested fields, and custom object conversion.
- Firebase release notes — Current Firebase SDK and CLI version baseline.
- Connect to the Firestore Emulator — Local emulator connection and production-difference guidance.