Cloudbox emulates the Firestore REST API (v1). The google-cloud-firestore Python SDK works
against it without modification.
Port: 8080 (override with CLOUDBOX_FIRESTORE_PORT)
import os
os.environ["FIRESTORE_EMULATOR_HOST"] = "localhost:8080"
from google.cloud import firestore
db = firestore.Client(project="local-project")The FIRESTORE_EMULATOR_HOST environment variable is the standard way to point the SDK at
any Firestore emulator. No credential setup is needed.
All document operations use the path pattern:
projects/{project}/databases/{database}/documents/{collection}/{document}
The default database is (default).
POST /v1/projects/{project}/databases/{database}/documents/{collection}
{
"fields": {
"name": { "stringValue": "Alice" },
"age": { "integerValue": "30" }
}
}Generates a random document ID. Returns the created document.
POST /v1/projects/{project}/databases/{database}/documents/{collection}/{doc_id}
Same body as above. If the document already exists it is overwritten.
GET /v1/projects/{project}/databases/{database}/documents/{collection}/{doc_id}
Returns the document resource. 404 if not found.
PATCH /v1/projects/{project}/databases/{database}/documents/{collection}/{doc_id}
?updateMask.fieldPaths=name&updateMask.fieldPaths=age
Merges the supplied fields into the document. Without updateMask, replaces all fields.
Supports currentDocument.exists and currentDocument.updateTime preconditions via query
parameters.
DELETE /v1/projects/{project}/databases/{database}/documents/{collection}/{doc_id}
200 on success. Supports currentDocument.exists precondition.
POST /v1/projects/{project}/databases/{database}/documents:batchGet
{
"documents": [
"projects/local-project/databases/(default)/documents/users/alice",
"projects/local-project/databases/(default)/documents/users/bob"
]
}Returns one result per requested document:
[
{ "found": { "name": "...", "fields": {...}, "readTime": "..." }, "readTime": "..." },
{ "missing": "projects/.../users/bob", "readTime": "..." }
]POST /v1/projects/{project}/databases/{database}:batchWrite
{
"writes": [
{ "update": { "name": "projects/.../users/alice", "fields": { "score": { "integerValue": "99" } } } },
{ "delete": "projects/.../users/bob" }
]
}Applies a list of mutations atomically. Supported write types: update, delete,
transform (field transforms). Returns writeResults and status for each write.
POST /v1/projects/{project}/databases/{database}/documents:runQuery
Or against a subcollection parent:
POST /v1/{parent_path}/documents:runQuery
{
"structuredQuery": {
"from": [{ "collectionId": "users" }],
"where": {
"fieldFilter": {
"field": { "fieldPath": "age" },
"op": "GREATER_THAN_OR_EQUAL",
"value": { "integerValue": "18" }
}
},
"orderBy": [{ "field": { "fieldPath": "age" }, "direction": "ASCENDING" }],
"limit": 10,
"offset": 0
}
}Returns a list of {"document": {...}, "readTime": "..."} objects.
Supported filter operators: EQUAL, NOT_EQUAL, LESS_THAN, LESS_THAN_OR_EQUAL,
GREATER_THAN, GREATER_THAN_OR_EQUAL, ARRAY_CONTAINS, IN, ARRAY_CONTAINS_ANY,
NOT_IN.
Composite filters (AND, OR) are supported via compositeFilter.
allDescendants: true in the from clause enables collection group queries.
POST /v1/projects/{project}/databases/{database}/documents:runAggregationQuery
{
"structuredAggregationQuery": {
"structuredQuery": { "from": [{ "collectionId": "orders" }] },
"aggregations": [
{ "alias": "total_count", "count": {} },
{ "alias": "total_revenue", "sum": { "field": { "fieldPath": "amount" } } },
{ "alias": "avg_amount", "avg": { "field": { "fieldPath": "amount" } } }
]
}
}Supported aggregations: count (with optional upTo), sum, avg. Returns a list with
one {"result": {"aggregateFields": {...}}, "readTime": "..."} object.
POST /v1/projects/{project}/databases/{database}:beginTransaction
Returns { "transaction": "<base64-id>" }. The transaction ID can be passed with read
operations (reads inside a transaction are not yet isolated, but the ID is accepted).
POST /v1/projects/{project}/databases/{database}:commit
{
"transaction": "<base64-id>",
"writes": [
{ "update": { "name": "...", "fields": {...} } }
]
}Applies the writes and returns writeResults and commitTime.
POST /v1/projects/{project}/databases/{database}:rollback
{ "transaction": "<base64-id>" }No-op — returns {}. Transactions are not currently tracked between begin and rollback.
Fields use Firestore's typed value format:
| Type | JSON key | Example |
|---|---|---|
| String | stringValue |
{"stringValue": "hello"} |
| Integer | integerValue |
{"integerValue": "42"} |
| Double | doubleValue |
{"doubleValue": 3.14} |
| Boolean | booleanValue |
{"booleanValue": true} |
| Null | nullValue |
{"nullValue": "NULL_VALUE"} |
| Bytes | bytesValue |
{"bytesValue": "<base64>"} |
| Timestamp | timestampValue |
{"timestampValue": "2024-01-01T00:00:00Z"} |
| Reference | referenceValue |
{"referenceValue": "projects/p/databases/d/documents/..."} |
| GeoPoint | geoPointValue |
{"geoPointValue": {"latitude": 37.4, "longitude": -122.0}} |
| Array | arrayValue |
{"arrayValue": {"values": [...]}} |
| Map | mapValue |
{"mapValue": {"fields": {...}}} |
| Feature | Notes |
|---|---|
Real-time listeners (on_snapshot) |
Not implemented — long-lived SSE/WebSocket not supported |
| Transaction isolation | Reads inside a transaction are not isolated from concurrent writes |
| Composite index enforcement | Queries that would require a composite index in production succeed locally |
| Security rules | Not evaluated — all operations are permitted |
| Database management (create/delete databases) | Always uses (default) database |
uv run python examples/firestore/crud.py
uv run python examples/firestore/queries.py