Configure GCP Firestore Fields

The gcp:firestore/field:Field resource, part of the Pulumi GCP provider, configures single-field behavior within Firestore collection groups: indexes that enable queries and TTL policies that trigger automatic deletion. This guide focuses on three capabilities: query index configuration, TTL-based document expiration, and configuration inheritance overrides.

Field configuration applies to collection groups, which represent all collections with the same ID across your database. The examples are intentionally small. Combine them with your own Firestore database and collection structure.

Configure indexes for query optimization

Applications that query Firestore collections need indexes to support efficient filtering and sorting.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const database = new gcp.firestore.Database("database", {
    project: "my-project-name",
    name: "database-id",
    locationId: "nam5",
    type: "FIRESTORE_NATIVE",
    deleteProtectionState: "DELETE_PROTECTION_ENABLED",
    deletionPolicy: "DELETE",
});
const basic = new gcp.firestore.Field("basic", {
    project: "my-project-name",
    database: database.name,
    collection: "chatrooms__12618",
    field: "basic",
    indexConfig: {
        indexes: [
            {
                order: "ASCENDING",
                queryScope: "COLLECTION_GROUP",
            },
            {
                arrayConfig: "CONTAINS",
            },
        ],
    },
});
import pulumi
import pulumi_gcp as gcp

database = gcp.firestore.Database("database",
    project="my-project-name",
    name="database-id",
    location_id="nam5",
    type="FIRESTORE_NATIVE",
    delete_protection_state="DELETE_PROTECTION_ENABLED",
    deletion_policy="DELETE")
basic = gcp.firestore.Field("basic",
    project="my-project-name",
    database=database.name,
    collection="chatrooms__12618",
    field="basic",
    index_config={
        "indexes": [
            {
                "order": "ASCENDING",
                "query_scope": "COLLECTION_GROUP",
            },
            {
                "array_config": "CONTAINS",
            },
        ],
    })
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/firestore"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		database, err := firestore.NewDatabase(ctx, "database", &firestore.DatabaseArgs{
			Project:               pulumi.String("my-project-name"),
			Name:                  pulumi.String("database-id"),
			LocationId:            pulumi.String("nam5"),
			Type:                  pulumi.String("FIRESTORE_NATIVE"),
			DeleteProtectionState: pulumi.String("DELETE_PROTECTION_ENABLED"),
			DeletionPolicy:        pulumi.String("DELETE"),
		})
		if err != nil {
			return err
		}
		_, err = firestore.NewField(ctx, "basic", &firestore.FieldArgs{
			Project:    pulumi.String("my-project-name"),
			Database:   database.Name,
			Collection: pulumi.String("chatrooms__12618"),
			Field:      pulumi.String("basic"),
			IndexConfig: &firestore.FieldIndexConfigArgs{
				Indexes: firestore.FieldIndexConfigIndexArray{
					&firestore.FieldIndexConfigIndexArgs{
						Order:      pulumi.String("ASCENDING"),
						QueryScope: pulumi.String("COLLECTION_GROUP"),
					},
					&firestore.FieldIndexConfigIndexArgs{
						ArrayConfig: pulumi.String("CONTAINS"),
					},
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var database = new Gcp.Firestore.Database("database", new()
    {
        Project = "my-project-name",
        Name = "database-id",
        LocationId = "nam5",
        Type = "FIRESTORE_NATIVE",
        DeleteProtectionState = "DELETE_PROTECTION_ENABLED",
        DeletionPolicy = "DELETE",
    });

    var basic = new Gcp.Firestore.Field("basic", new()
    {
        Project = "my-project-name",
        Database = database.Name,
        Collection = "chatrooms__12618",
        FieldId = "basic",
        IndexConfig = new Gcp.Firestore.Inputs.FieldIndexConfigArgs
        {
            Indexes = new[]
            {
                new Gcp.Firestore.Inputs.FieldIndexConfigIndexArgs
                {
                    Order = "ASCENDING",
                    QueryScope = "COLLECTION_GROUP",
                },
                new Gcp.Firestore.Inputs.FieldIndexConfigIndexArgs
                {
                    ArrayConfig = "CONTAINS",
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.firestore.Database;
import com.pulumi.gcp.firestore.DatabaseArgs;
import com.pulumi.gcp.firestore.Field;
import com.pulumi.gcp.firestore.FieldArgs;
import com.pulumi.gcp.firestore.inputs.FieldIndexConfigArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var database = new Database("database", DatabaseArgs.builder()
            .project("my-project-name")
            .name("database-id")
            .locationId("nam5")
            .type("FIRESTORE_NATIVE")
            .deleteProtectionState("DELETE_PROTECTION_ENABLED")
            .deletionPolicy("DELETE")
            .build());

        var basic = new Field("basic", FieldArgs.builder()
            .project("my-project-name")
            .database(database.name())
            .collection("chatrooms__12618")
            .field("basic")
            .indexConfig(FieldIndexConfigArgs.builder()
                .indexes(                
                    FieldIndexConfigIndexArgs.builder()
                        .order("ASCENDING")
                        .queryScope("COLLECTION_GROUP")
                        .build(),
                    FieldIndexConfigIndexArgs.builder()
                        .arrayConfig("CONTAINS")
                        .build())
                .build())
            .build());

    }
}
resources:
  database:
    type: gcp:firestore:Database
    properties:
      project: my-project-name
      name: database-id
      locationId: nam5
      type: FIRESTORE_NATIVE
      deleteProtectionState: DELETE_PROTECTION_ENABLED
      deletionPolicy: DELETE
  basic:
    type: gcp:firestore:Field
    properties:
      project: my-project-name
      database: ${database.name}
      collection: chatrooms__12618
      field: basic
      indexConfig:
        indexes:
          - order: ASCENDING
            queryScope: COLLECTION_GROUP
          - arrayConfig: CONTAINS

The indexConfig property defines which query patterns this field supports. Each entry in the indexes array enables a specific query type: order specifies ascending or descending sorting, while arrayConfig enables array-contains queries. The queryScope property controls whether the index applies to single collections or across all collections in the group.

Enable automatic document expiration with TTL

Session data and temporary records often need automatic cleanup after a certain period.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const database = new gcp.firestore.Database("database", {
    project: "my-project-name",
    name: "database-id",
    locationId: "nam5",
    type: "FIRESTORE_NATIVE",
    deleteProtectionState: "DELETE_PROTECTION_ENABLED",
    deletionPolicy: "DELETE",
});
const timestamp = new gcp.firestore.Field("timestamp", {
    project: "my-project-name",
    database: database.name,
    collection: "chatrooms",
    field: "timestamp",
    ttlConfig: {},
    indexConfig: {},
});
import pulumi
import pulumi_gcp as gcp

database = gcp.firestore.Database("database",
    project="my-project-name",
    name="database-id",
    location_id="nam5",
    type="FIRESTORE_NATIVE",
    delete_protection_state="DELETE_PROTECTION_ENABLED",
    deletion_policy="DELETE")
timestamp = gcp.firestore.Field("timestamp",
    project="my-project-name",
    database=database.name,
    collection="chatrooms",
    field="timestamp",
    ttl_config={},
    index_config={})
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/firestore"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		database, err := firestore.NewDatabase(ctx, "database", &firestore.DatabaseArgs{
			Project:               pulumi.String("my-project-name"),
			Name:                  pulumi.String("database-id"),
			LocationId:            pulumi.String("nam5"),
			Type:                  pulumi.String("FIRESTORE_NATIVE"),
			DeleteProtectionState: pulumi.String("DELETE_PROTECTION_ENABLED"),
			DeletionPolicy:        pulumi.String("DELETE"),
		})
		if err != nil {
			return err
		}
		_, err = firestore.NewField(ctx, "timestamp", &firestore.FieldArgs{
			Project:     pulumi.String("my-project-name"),
			Database:    database.Name,
			Collection:  pulumi.String("chatrooms"),
			Field:       pulumi.String("timestamp"),
			TtlConfig:   &firestore.FieldTtlConfigArgs{},
			IndexConfig: &firestore.FieldIndexConfigArgs{},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var database = new Gcp.Firestore.Database("database", new()
    {
        Project = "my-project-name",
        Name = "database-id",
        LocationId = "nam5",
        Type = "FIRESTORE_NATIVE",
        DeleteProtectionState = "DELETE_PROTECTION_ENABLED",
        DeletionPolicy = "DELETE",
    });

    var timestamp = new Gcp.Firestore.Field("timestamp", new()
    {
        Project = "my-project-name",
        Database = database.Name,
        Collection = "chatrooms",
        FieldId = "timestamp",
        TtlConfig = null,
        IndexConfig = null,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.firestore.Database;
import com.pulumi.gcp.firestore.DatabaseArgs;
import com.pulumi.gcp.firestore.Field;
import com.pulumi.gcp.firestore.FieldArgs;
import com.pulumi.gcp.firestore.inputs.FieldTtlConfigArgs;
import com.pulumi.gcp.firestore.inputs.FieldIndexConfigArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var database = new Database("database", DatabaseArgs.builder()
            .project("my-project-name")
            .name("database-id")
            .locationId("nam5")
            .type("FIRESTORE_NATIVE")
            .deleteProtectionState("DELETE_PROTECTION_ENABLED")
            .deletionPolicy("DELETE")
            .build());

        var timestamp = new Field("timestamp", FieldArgs.builder()
            .project("my-project-name")
            .database(database.name())
            .collection("chatrooms")
            .field("timestamp")
            .ttlConfig(FieldTtlConfigArgs.builder()
                .build())
            .indexConfig(FieldIndexConfigArgs.builder()
                .build())
            .build());

    }
}
resources:
  database:
    type: gcp:firestore:Database
    properties:
      project: my-project-name
      name: database-id
      locationId: nam5
      type: FIRESTORE_NATIVE
      deleteProtectionState: DELETE_PROTECTION_ENABLED
      deletionPolicy: DELETE
  timestamp:
    type: gcp:firestore:Field
    properties:
      project: my-project-name
      database: ${database.name}
      collection: chatrooms
      field: timestamp
      ttlConfig: {}
      indexConfig: {}

Setting ttlConfig to an empty block enables TTL-based deletion. Firestore automatically removes documents when the timestamp in this field expires. The indexConfig property is set to an empty block here, which disables all indexes on the field while keeping TTL active.

Override inherited index configuration

Collection groups inherit index settings from parent configurations, but sometimes you need field-specific overrides.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const database = new gcp.firestore.Database("database", {
    project: "my-project-name",
    name: "database-id",
    locationId: "nam5",
    type: "FIRESTORE_NATIVE",
    deleteProtectionState: "DELETE_PROTECTION_ENABLED",
    deletionPolicy: "DELETE",
});
const matchOverride = new gcp.firestore.Field("match_override", {
    project: "my-project-name",
    database: database.name,
    collection: "chatrooms__32270",
    field: "field_with_same_configuration_as_ancestor",
    indexConfig: {
        indexes: [
            {
                order: "ASCENDING",
            },
            {
                order: "DESCENDING",
            },
            {
                arrayConfig: "CONTAINS",
            },
        ],
    },
});
import pulumi
import pulumi_gcp as gcp

database = gcp.firestore.Database("database",
    project="my-project-name",
    name="database-id",
    location_id="nam5",
    type="FIRESTORE_NATIVE",
    delete_protection_state="DELETE_PROTECTION_ENABLED",
    deletion_policy="DELETE")
match_override = gcp.firestore.Field("match_override",
    project="my-project-name",
    database=database.name,
    collection="chatrooms__32270",
    field="field_with_same_configuration_as_ancestor",
    index_config={
        "indexes": [
            {
                "order": "ASCENDING",
            },
            {
                "order": "DESCENDING",
            },
            {
                "array_config": "CONTAINS",
            },
        ],
    })
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/firestore"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		database, err := firestore.NewDatabase(ctx, "database", &firestore.DatabaseArgs{
			Project:               pulumi.String("my-project-name"),
			Name:                  pulumi.String("database-id"),
			LocationId:            pulumi.String("nam5"),
			Type:                  pulumi.String("FIRESTORE_NATIVE"),
			DeleteProtectionState: pulumi.String("DELETE_PROTECTION_ENABLED"),
			DeletionPolicy:        pulumi.String("DELETE"),
		})
		if err != nil {
			return err
		}
		_, err = firestore.NewField(ctx, "match_override", &firestore.FieldArgs{
			Project:    pulumi.String("my-project-name"),
			Database:   database.Name,
			Collection: pulumi.String("chatrooms__32270"),
			Field:      pulumi.String("field_with_same_configuration_as_ancestor"),
			IndexConfig: &firestore.FieldIndexConfigArgs{
				Indexes: firestore.FieldIndexConfigIndexArray{
					&firestore.FieldIndexConfigIndexArgs{
						Order: pulumi.String("ASCENDING"),
					},
					&firestore.FieldIndexConfigIndexArgs{
						Order: pulumi.String("DESCENDING"),
					},
					&firestore.FieldIndexConfigIndexArgs{
						ArrayConfig: pulumi.String("CONTAINS"),
					},
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var database = new Gcp.Firestore.Database("database", new()
    {
        Project = "my-project-name",
        Name = "database-id",
        LocationId = "nam5",
        Type = "FIRESTORE_NATIVE",
        DeleteProtectionState = "DELETE_PROTECTION_ENABLED",
        DeletionPolicy = "DELETE",
    });

    var matchOverride = new Gcp.Firestore.Field("match_override", new()
    {
        Project = "my-project-name",
        Database = database.Name,
        Collection = "chatrooms__32270",
        FieldId = "field_with_same_configuration_as_ancestor",
        IndexConfig = new Gcp.Firestore.Inputs.FieldIndexConfigArgs
        {
            Indexes = new[]
            {
                new Gcp.Firestore.Inputs.FieldIndexConfigIndexArgs
                {
                    Order = "ASCENDING",
                },
                new Gcp.Firestore.Inputs.FieldIndexConfigIndexArgs
                {
                    Order = "DESCENDING",
                },
                new Gcp.Firestore.Inputs.FieldIndexConfigIndexArgs
                {
                    ArrayConfig = "CONTAINS",
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.firestore.Database;
import com.pulumi.gcp.firestore.DatabaseArgs;
import com.pulumi.gcp.firestore.Field;
import com.pulumi.gcp.firestore.FieldArgs;
import com.pulumi.gcp.firestore.inputs.FieldIndexConfigArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var database = new Database("database", DatabaseArgs.builder()
            .project("my-project-name")
            .name("database-id")
            .locationId("nam5")
            .type("FIRESTORE_NATIVE")
            .deleteProtectionState("DELETE_PROTECTION_ENABLED")
            .deletionPolicy("DELETE")
            .build());

        var matchOverride = new Field("matchOverride", FieldArgs.builder()
            .project("my-project-name")
            .database(database.name())
            .collection("chatrooms__32270")
            .field("field_with_same_configuration_as_ancestor")
            .indexConfig(FieldIndexConfigArgs.builder()
                .indexes(                
                    FieldIndexConfigIndexArgs.builder()
                        .order("ASCENDING")
                        .build(),
                    FieldIndexConfigIndexArgs.builder()
                        .order("DESCENDING")
                        .build(),
                    FieldIndexConfigIndexArgs.builder()
                        .arrayConfig("CONTAINS")
                        .build())
                .build())
            .build());

    }
}
resources:
  database:
    type: gcp:firestore:Database
    properties:
      project: my-project-name
      name: database-id
      locationId: nam5
      type: FIRESTORE_NATIVE
      deleteProtectionState: DELETE_PROTECTION_ENABLED
      deletionPolicy: DELETE
  matchOverride:
    type: gcp:firestore:Field
    name: match_override
    properties:
      project: my-project-name
      database: ${database.name}
      collection: chatrooms__32270
      field: field_with_same_configuration_as_ancestor
      indexConfig:
        indexes:
          - order: ASCENDING
          - order: DESCENDING
          - arrayConfig: CONTAINS

This configuration replaces any inherited indexes with the specified set. The indexes array defines three query patterns: ascending order, descending order, and array-contains. Without queryScope, indexes default to collection-level scope rather than collection-group scope.

Beyond these examples

These snippets focus on specific field-level features: index configuration for queries, TTL-based document expiration, and configuration inheritance and overrides. They’re intentionally minimal rather than complete database schemas.

The examples require pre-existing infrastructure such as a Firestore database (created via gcp.firestore.Database) and a GCP project with Firestore enabled. They focus on field configuration rather than database provisioning.

To keep things focused, common field patterns are omitted, including:

  • Wildcard field configuration (field: ‘*’)
  • Composite indexes spanning multiple fields
  • Index exemptions and disabling
  • Vector search indexes

These omissions are intentional: the goal is to illustrate how each field feature is wired, not provide drop-in database modules. See the Firestore Field resource reference for all available configuration options.

Let's configure GCP Firestore Fields

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Prerequisites & Setup
Do I need to create a Firestore database before creating field overrides?
Yes, you must have an existing Firestore database in your project. Create a gcp.firestore.Database resource with locationId set before creating Field resources.
Field Configuration & Immutability
What properties can't I change after creating a field override?
The collection, field, project, and database properties are all immutable and cannot be changed after creation.
What's the default database ID if I don't specify one?
The database property defaults to "(default)" if not specified.
Index Management
How do I disable all indexes on a field?
Configure indexConfig with an empty block (indexConfig: {}) to disable all indexes on the field.
TTL Configuration
How do I configure TTL (time-to-live) on a field?
To enable TTL, set ttlConfig to an empty block (ttlConfig: {}). To disable TTL, leave ttlConfig unset or remove it when updating the resource.
Advanced Features
Can I configure all fields in a collection group at once?
Yes, set field to "*" to create a wildcard configuration that applies to all fields in the collection group.

Using a different cloud?

Explore database guides for other cloud providers: