# Firebase Firestore

> Read and write documents in Firebase Firestore - query collections, update fields, run atomic counters and aggregate without reading.

App · Databases · 20 actions. Web page: https://docs.lodol.com/docs/integrations/firebase-firestore

## Actions

### Add to an array field

Add values to an array on a document, skipping any already there, so running the step twice does not duplicate an entry.

**Inputs**

- Document path (`document`, text, required): Which document, as collection/document - 'users/alice'. The path from an earlier Firestore step works as it stands, and so does the full projects/... resource name.
- Field (`field_name`, text, required): Which field to change. A dot means a nested field, so "stats.views" is views inside the stats object.
- Values (`values`, list, required): The values to use. Values are ordinary JSON. For the types JSON has no syntax for, use {"$timestamp": "2026-09-05T10:30:00Z"}, {"$geopoint": {"latitude": 52.5, "longitude": 13.4}}, {"$reference": "users/alice"} or {"$bytes": "<base64>"}.

**Outputs**

- `field` (text)
- `path` (text)
- `updated_time` (text)
- `value`

**Example output**

```json
{
  "field": "tags",
  "path": "users/alice",
  "updated_time": "2026-09-05T11:12:00.000000Z",
  "value": null
}
```

### Count documents

Count the documents matching a filter without reading them, which costs a fraction of the equivalent query.

**Inputs**

- Collection (`collection`, text, required): The collection to work in. Pick a root collection, or type a path to reach a subcollection - 'users' or 'users/alice/pets'. A collection path always has an odd number of segments.
- Filters (`filters`, list): Which documents to match. Each filter is a field, an operator and a value - for example {"field": "status", "operator": "EQUAL", "value": "open"}. The four IS_NULL/IS_NOT_NULL/IS_NAN/IS_NOT_NAN operators test the field on their own and take no value; IN, NOT_IN and ARRAY_CONTAINS_ANY take a list. Leave empty to match every document. Filtering or sorting on more than one field needs a composite index - Firestore's error carries a link that creates it.
- Combine filters with (`combine_filters`, text): Whether a document has to match every filter or just one. One of: `AND` (Match every filter (AND)), `OR` (Match any filter (OR)). Default: `AND`.
- Include subcollections with the same name (`include_subcollections`, true/false): Search every collection with this id anywhere in the database, not just the one at this path - Firestore calls it a collection group query. Use it to read, say, every 'comments' subcollection under every post at once. Default: `false`.

**Outputs**

- `collection` (text)
- `count` (number)

**Example output**

```json
{
  "collection": "users",
  "count": 128
}
```

### Create document

Add a document to a collection. Leave the id empty and Firestore mints one.

**Inputs**

- Collection (`collection`, text, required): The collection to work in. Pick a root collection, or type a path to reach a subcollection - 'users' or 'users/alice/pets'. A collection path always has an odd number of segments.
- Fields (`fields`, object): The fields to store, as an object. A dot in a field name means a nested field, so "address.city" writes city inside the address object and leaves the rest of address alone. Values are ordinary JSON. For the types JSON has no syntax for, use {"$timestamp": "2026-09-05T10:30:00Z"}, {"$geopoint": {"latitude": 52.5, "longitude": 13.4}}, {"$reference": "users/alice"} or {"$bytes": "<base64>"}.
- Document id (`document_id`, text): The id to give the document. Leave empty for a Firestore-generated one, which spreads writes evenly; a counting id such as user1, user2 concentrates them on one part of the database and slows it down.

**Outputs**

- `collection` (text)
- `created_time` (text)
- `fields` (object)
- `id` (text)
- `name` (text)
- `path` (text)
- `updated_time` (text)

**Example output**

```json
{
  "collection": "users",
  "created_time": "2026-09-05T10:30:00.123456Z",
  "fields": {
    "active": true,
    "name": "Ada",
    "score": 42
  },
  "id": "kj4Yd2mQpLxV8nRt",
  "name": "projects/demo-project/databases/(default)/documents/users/kj4Yd2mQpLxV8nRt",
  "path": "users/kj4Yd2mQpLxV8nRt",
  "updated_time": "2026-09-05T10:30:00.123456Z"
}
```

### Create or update document

Write a document at a known path, creating it if it is not there. Choose whether to merge with what is already stored or replace it.

**Inputs**

- Document path (`document`, text, required): Which document, as collection/document - 'users/alice'. The path from an earlier Firestore step works as it stands, and so does the full projects/... resource name.
- Fields (`fields`, object, required): The fields to write. A dot in a field name means a nested field, so "address.city" writes city inside the address object and leaves the rest of address alone. Values are ordinary JSON. For the types JSON has no syntax for, use {"$timestamp": "2026-09-05T10:30:00Z"}, {"$geopoint": {"latitude": 52.5, "longitude": 13.4}}, {"$reference": "users/alice"} or {"$bytes": "<base64>"}.
- If the document already exists (`mode`, text): Merge keeps fields you have not listed. Replace deletes them, leaving the document holding exactly what is here. One of: `merge` (Merge - keep any field not listed here), `replace` (Replace - delete any field not listed here). Default: `merge`.

**Outputs**

- `collection` (text)
- `created_time` (text)
- `fields` (object)
- `id` (text)
- `name` (text)
- `path` (text)
- `updated_time` (text)

**Example output**

```json
{
  "collection": "users",
  "created_time": "2026-09-01T08:00:00.000000Z",
  "fields": {
    "name": "Ada",
    "score": 42
  },
  "id": "alice",
  "name": "projects/demo-project/databases/(default)/documents/users/alice",
  "path": "users/alice",
  "updated_time": "2026-09-05T11:05:00.000000Z"
}
```

### Create several documents

Create a list of documents in one collection, all or nothing. Fails without writing anything if any of the ids is already taken.

**Inputs**

- Collection (`collection`, text, required): The collection to work in. Pick a root collection, or type a path to reach a subcollection - 'users' or 'users/alice/pets'. A collection path always has an odd number of segments.
- Documents (`documents`, list, required): Each entry needs an 'id' and the fields to store alongside it. At most 500 per call. Values are ordinary JSON. For the types JSON has no syntax for, use {"$timestamp": "2026-09-05T10:30:00Z"}, {"$geopoint": {"latitude": 52.5, "longitude": 13.4}}, {"$reference": "users/alice"} or {"$bytes": "<base64>"}.

**Outputs**

- `collection` (text)
- `commit_time` (text)
- `count` (number)
- `paths` (list)

**Example output**

```json
{
  "collection": "users",
  "commit_time": "2026-09-05T11:20:00.000000Z",
  "count": 2,
  "paths": [
    "users/alice",
    "users/bob"
  ]
}
```

### Delete document

Delete one document. Any subcollections under it are left in place - Firestore does not delete those with the parent.

**Inputs**

- Document path (`document`, text, required): Which document, as collection/document - 'users/alice'. The path from an earlier Firestore step works as it stands, and so does the full projects/... resource name.
- Fail if it is not there (`must_exist`, true/false): Firestore treats deleting a missing document as a success. Turn this on to hear about a path that matched nothing instead. Default: `false`.

**Outputs**

- `deleted` (true/false)
- `path` (text)

**Example output**

```json
{
  "deleted": true,
  "path": "users/alice"
}
```

### Delete several documents

Delete a list of documents by path, all or nothing.

**Inputs**

- Document paths (`documents`, list, required): The paths to delete, such as users/alice. At most 500 at a time.

**Outputs**

- `commit_time` (text)
- `count` (number)
- `deleted` (true/false)
- `paths` (list)

**Example output**

```json
{
  "commit_time": "2026-09-05T11:25:00.000000Z",
  "count": 2,
  "deleted": true,
  "paths": [
    "users/alice",
    "users/bob"
  ]
}
```

### Empty a collection

Delete the documents in a collection, in batches. Subcollections under those documents are left alone - empty them by naming their own path.

**Inputs**

- Collection (`collection`, text, required): The collection to work in. Pick a root collection, or type a path to reach a subcollection - 'users' or 'users/alice/pets'. A collection path always has an odd number of segments.
- Delete at most (`limit`, number): How many documents to delete in this run, at most 500. If reached_limit comes back true there are more left - run the step again. From 1 to 500. Default: `25`.

**Outputs**

- `collection` (text)
- `deleted_count` (number)
- `paths` (list)
- `reached_limit` (true/false)

**Example output**

```json
{
  "collection": "scratch",
  "deleted_count": 3,
  "paths": [
    "scratch/a",
    "scratch/b",
    "scratch/c"
  ],
  "reached_limit": false
}
```

### Get database settings

Read one database's location, edition, concurrency mode and delete protection.

**Inputs**

- Database (`database`, text): Which database to read. Leave empty for the one this connection uses.

**Outputs**

- `concurrency_mode` (text)
- `created_time` (text)
- `delete_protection` (text)
- `edition` (text)
- `id` (text)
- `location` (text)
- `name` (text)
- `point_in_time_recovery` (text)
- `type` (text)
- `updated_time` (text)

**Example output**

```json
{
  "concurrency_mode": "PESSIMISTIC",
  "created_time": "2026-01-04T09:00:00.000000Z",
  "delete_protection": "DELETE_PROTECTION_DISABLED",
  "edition": "STANDARD",
  "id": "(default)",
  "location": "nam5",
  "name": "projects/demo-project/databases/(default)",
  "point_in_time_recovery": "POINT_IN_TIME_RECOVERY_DISABLED",
  "type": "FIRESTORE_NATIVE",
  "updated_time": "2026-01-04T09:00:00.000000Z"
}
```

### Get document

Read one document by its path.

**Inputs**

- Document path (`document`, text, required): Which document, as collection/document - 'users/alice'. The path from an earlier Firestore step works as it stands, and so does the full projects/... resource name.
- Only these fields (`select_fields`, list): Return just these fields instead of the whole document. Leave empty for everything. Reading fewer fields does not reduce what Firestore charges for the read, but it does keep a large document out of the workflow.

**Outputs**

- `collection` (text)
- `created_time` (text)
- `fields` (object)
- `id` (text)
- `name` (text)
- `path` (text)
- `updated_time` (text)

**Example output**

```json
{
  "collection": "users",
  "created_time": "2026-09-01T08:00:00.000000Z",
  "fields": {
    "active": true,
    "name": "Ada",
    "score": 42
  },
  "id": "alice",
  "name": "projects/demo-project/databases/(default)/documents/users/alice",
  "path": "users/alice",
  "updated_time": "2026-09-05T10:30:00.123456Z"
}
```

### Get several documents

Read a list of documents by path in one round trip. Paths that matched nothing are reported separately rather than failing the step.

**Inputs**

- Document paths (`documents`, list, required): The paths to read, such as users/alice. At most 500 at a time.
- Only these fields (`select_fields`, list): Return just these fields instead of the whole document. Leave empty for everything. Reading fewer fields does not reduce what Firestore charges for the read, but it does keep a large document out of the workflow.

**Outputs**

- `count` (number)
- `documents` (list)
- `missing` (list)
- `missing_count` (number)

**Example output**

```json
{
  "count": 1,
  "documents": [
    {
      "collection": "users",
      "created_time": "2026-09-01T08:00:00.000000Z",
      "fields": {
        "name": "Ada"
      },
      "id": "alice",
      "name": "projects/demo-project/databases/(default)/documents/users/alice",
      "path": "users/alice",
      "updated_time": "2026-09-05T10:30:00.123456Z"
    }
  ],
  "missing": [
    "users/nobody"
  ],
  "missing_count": 1
}
```

### Increment a field

Add to a number stored on a document, atomically. Two workflows doing this at once cannot lose each other's change the way reading and writing back would.

**Inputs**

- Document path (`document`, text, required): Which document, as collection/document - 'users/alice'. The path from an earlier Firestore step works as it stands, and so does the full projects/... resource name.
- Field (`field_name`, text, required): Which field to change. A dot means a nested field, so "stats.views" is views inside the stats object.
- Add (`amount`, number): How much to add. Use a negative number to subtract. A field that does not exist yet starts from zero, and a document that does not exist yet is created. Default: `1`.

**Outputs**

- `field` (text)
- `path` (text)
- `updated_time` (text)
- `value`

**Example output**

```json
{
  "field": "total",
  "path": "counters/visits",
  "updated_time": "2026-09-05T11:10:00.000000Z",
  "value": 129
}
```

### List collections

List the collections at the top of the database, or the subcollections under one document.

**Inputs**

- Under this document (`parent_document`, text): Leave empty for the database's root collections, or give a document path such as users/alice to list its subcollections.
- Maximum documents (`limit`, number): How many documents to return, at most 300. Firestore charges per document read, so keep this to what the workflow will use. From 1 to 300. Default: `25`.

**Outputs**

- `collections` (list)
- `count` (number)
- `parent` (text)

**Example output**

```json
{
  "collections": [
    "users",
    "orders",
    "products"
  ],
  "count": 3,
  "parent": ""
}
```

### List databases

List the Firestore databases in this project, with their locations and editions.

**Inputs**: None.

**Outputs**

- `count` (number)
- `databases` (list)
- `project` (text)

**Example output**

```json
{
  "count": 1,
  "databases": [
    {
      "concurrency_mode": "PESSIMISTIC",
      "created_time": "2026-01-04T09:00:00.000000Z",
      "delete_protection": "DELETE_PROTECTION_DISABLED",
      "edition": "STANDARD",
      "id": "(default)",
      "location": "nam5",
      "name": "projects/demo-project/databases/(default)",
      "point_in_time_recovery": "POINT_IN_TIME_RECOVERY_DISABLED",
      "type": "FIRESTORE_NATIVE",
      "updated_time": "2026-01-04T09:00:00.000000Z"
    }
  ],
  "project": "demo-project"
}
```

### List documents

List the documents in a collection, a page at a time. Use Query documents to filter them.

**Inputs**

- Collection (`collection`, text, required): The collection to work in. Pick a root collection, or type a path to reach a subcollection - 'users' or 'users/alice/pets'. A collection path always has an odd number of segments.
- Maximum documents (`limit`, number): How many documents to return, at most 300. Firestore charges per document read, so keep this to what the workflow will use. From 1 to 300. Default: `25`.
- Sort by field (`order_by`, text): Which field to sort on. Leave empty to let Firestore return documents in document-id order. A document missing this field is left out of the results entirely - that is Firestore's behaviour for a sorted query, not a bug in the step.
- Sort direction (`direction`, text): Which way to sort, when a sort field is set. One of: `ASCENDING` (Ascending (A-Z, 0-9)), `DESCENDING`. Default: `ASCENDING`.
- Page token (`page_token`, text): Continue from a previous run: pass the next_page_token that run returned. Leave empty for the first page.
- Only these fields (`select_fields`, list): Return just these fields instead of the whole document. Leave empty for everything. Reading fewer fields does not reduce what Firestore charges for the read, but it does keep a large document out of the workflow.

**Outputs**

- `collection` (text)
- `count` (number)
- `documents` (list)
- `next_page_token` (text)

**Example output**

```json
{
  "collection": "users",
  "count": 1,
  "documents": [
    {
      "collection": "users",
      "created_time": "2026-09-01T08:00:00.000000Z",
      "fields": {
        "name": "Ada",
        "score": 42
      },
      "id": "alice",
      "name": "projects/demo-project/databases/(default)/documents/users/alice",
      "path": "users/alice",
      "updated_time": "2026-09-05T10:30:00.123456Z"
    }
  ],
  "next_page_token": null
}
```

### Query documents

Find the documents in a collection matching one or more field filters, sorted however you like.

**Inputs**

- Collection (`collection`, text, required): The collection to work in. Pick a root collection, or type a path to reach a subcollection - 'users' or 'users/alice/pets'. A collection path always has an odd number of segments.
- Filters (`filters`, list): Which documents to match. Each filter is a field, an operator and a value - for example {"field": "status", "operator": "EQUAL", "value": "open"}. The four IS_NULL/IS_NOT_NULL/IS_NAN/IS_NOT_NAN operators test the field on their own and take no value; IN, NOT_IN and ARRAY_CONTAINS_ANY take a list. Leave empty to match every document. Filtering or sorting on more than one field needs a composite index - Firestore's error carries a link that creates it.
- Combine filters with (`combine_filters`, text): Whether a document has to match every filter or just one. One of: `AND` (Match every filter (AND)), `OR` (Match any filter (OR)). Default: `AND`.
- Sort by field (`order_by`, text): Which field to sort on. Leave empty to let Firestore return documents in document-id order. A document missing this field is left out of the results entirely - that is Firestore's behaviour for a sorted query, not a bug in the step.
- Sort direction (`direction`, text): Which way to sort, when a sort field is set. One of: `ASCENDING` (Ascending (A-Z, 0-9)), `DESCENDING`. Default: `ASCENDING`.
- Maximum documents (`limit`, number): How many documents to return, at most 300. Firestore charges per document read, so keep this to what the workflow will use. From 1 to 300. Default: `25`.
- Skip (`offset`, number): Skip this many matches before returning any. Firestore still reads and charges for the skipped documents, so prefer a tighter filter where you can. At least 0. Default: `0`.
- Only these fields (`select_fields`, list): Return just these fields instead of the whole document. Leave empty for everything. Reading fewer fields does not reduce what Firestore charges for the read, but it does keep a large document out of the workflow.
- Include subcollections with the same name (`include_subcollections`, true/false): Search every collection with this id anywhere in the database, not just the one at this path - Firestore calls it a collection group query. Use it to read, say, every 'comments' subcollection under every post at once. Default: `false`.

**Outputs**

- `collection` (text)
- `count` (number)
- `documents` (list)

**Example output**

```json
{
  "collection": "users",
  "count": 1,
  "documents": [
    {
      "collection": "users",
      "created_time": "2026-09-01T08:00:00.000000Z",
      "fields": {
        "active": true,
        "name": "Ada",
        "score": 42
      },
      "id": "alice",
      "name": "projects/demo-project/databases/(default)/documents/users/alice",
      "path": "users/alice",
      "updated_time": "2026-09-05T10:30:00.123456Z"
    }
  ]
}
```

### Remove from an array field

Remove every occurrence of the given values from an array on a document.

**Inputs**

- Document path (`document`, text, required): Which document, as collection/document - 'users/alice'. The path from an earlier Firestore step works as it stands, and so does the full projects/... resource name.
- Field (`field_name`, text, required): Which field to change. A dot means a nested field, so "stats.views" is views inside the stats object.
- Values (`values`, list, required): The values to use. Values are ordinary JSON. For the types JSON has no syntax for, use {"$timestamp": "2026-09-05T10:30:00Z"}, {"$geopoint": {"latitude": 52.5, "longitude": 13.4}}, {"$reference": "users/alice"} or {"$bytes": "<base64>"}.

**Outputs**

- `field` (text)
- `path` (text)
- `updated_time` (text)
- `value`

**Example output**

```json
{
  "field": "tags",
  "path": "users/alice",
  "updated_time": "2026-09-05T11:13:00.000000Z",
  "value": null
}
```

### Stamp a field with the server time

Set a field to the moment Firestore applies the write, using Firestore's clock rather than this machine's.

**Inputs**

- Document path (`document`, text, required): Which document, as collection/document - 'users/alice'. The path from an earlier Firestore step works as it stands, and so does the full projects/... resource name.
- Field (`field_name`, text, required): Which field to change. A dot means a nested field, so "stats.views" is views inside the stats object.

**Outputs**

- `field` (text)
- `path` (text)
- `updated_time` (text)
- `value`

**Example output**

```json
{
  "field": "last_seen",
  "path": "users/alice",
  "updated_time": "2026-09-05T11:15:00.000000Z",
  "value": "2026-09-05T11:15:00.000000Z"
}
```

### Total or average a field

Add up or average a numeric field across the matching documents, without reading them.

**Inputs**

- Collection (`collection`, text, required): The collection to work in. Pick a root collection, or type a path to reach a subcollection - 'users' or 'users/alice/pets'. A collection path always has an odd number of segments.
- Calculate (`aggregation`, text, required): Which calculation to run over the matches. One of: `COUNT` (Count the documents), `SUM` (Add the field up), `AVG` (Average the field).
- Field (`field_name`, text): The numeric field to add up or average. Not needed when counting. Documents where the field is missing or is not a number are skipped.
- Filters (`filters`, list): Which documents to match. Each filter is a field, an operator and a value - for example {"field": "status", "operator": "EQUAL", "value": "open"}. The four IS_NULL/IS_NOT_NULL/IS_NAN/IS_NOT_NAN operators test the field on their own and take no value; IN, NOT_IN and ARRAY_CONTAINS_ANY take a list. Leave empty to match every document. Filtering or sorting on more than one field needs a composite index - Firestore's error carries a link that creates it.
- Combine filters with (`combine_filters`, text): Whether a document has to match every filter or just one. One of: `AND` (Match every filter (AND)), `OR` (Match any filter (OR)). Default: `AND`.
- Include subcollections with the same name (`include_subcollections`, true/false): Search every collection with this id anywhere in the database, not just the one at this path - Firestore calls it a collection group query. Use it to read, say, every 'comments' subcollection under every post at once. Default: `false`.

**Outputs**

- `aggregation` (text)
- `collection` (text)
- `field` (text)
- `result` (number)

**Example output**

```json
{
  "aggregation": "SUM",
  "collection": "orders",
  "field": "total",
  "result": 4820.5
}
```

### Update document

Change some fields on a document that already exists, leaving every other field alone. Fails if the document is not there.

**Inputs**

- Document path (`document`, text, required): Which document, as collection/document - 'users/alice'. The path from an earlier Firestore step works as it stands, and so does the full projects/... resource name.
- Fields to change (`fields`, object, required): Only these fields are touched; everything else on the document survives. A dot in a field name means a nested field, so "address.city" writes city inside the address object and leaves the rest of address alone. Setting a field to null stores a null. Values are ordinary JSON. For the types JSON has no syntax for, use {"$timestamp": "2026-09-05T10:30:00Z"}, {"$geopoint": {"latitude": 52.5, "longitude": 13.4}}, {"$reference": "users/alice"} or {"$bytes": "<base64>"}.

**Outputs**

- `collection` (text)
- `created_time` (text)
- `fields` (object)
- `id` (text)
- `name` (text)
- `path` (text)
- `updated_time` (text)

**Example output**

```json
{
  "collection": "users",
  "created_time": "2026-09-01T08:00:00.000000Z",
  "fields": {
    "active": true,
    "name": "Ada",
    "score": 43
  },
  "id": "alice",
  "name": "projects/demo-project/databases/(default)/documents/users/alice",
  "path": "users/alice",
  "updated_time": "2026-09-05T11:00:00.000000Z"
}
```
