Configure GCP Firestore Indexes

The gcp:firestore/index:Index resource, part of the Pulumi GCP provider, defines composite indexes that enable multi-field queries in Firestore Native and Datastore Mode databases. This guide focuses on three capabilities: multi-field query support with sort orders, Datastore Mode and MongoDB-compatible configurations, and vector embeddings and uniqueness constraints.

Indexes require an existing Firestore database and reference collections that will contain documents. The examples are intentionally small. Combine them with your own database provisioning and query patterns.

Index a collection with ordered fields

Most applications create composite indexes to support queries that filter or sort on multiple fields efficiently.

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_DISABLED",
    deletionPolicy: "DELETE",
});
const my_index = new gcp.firestore.Index("my-index", {
    project: "my-project-name",
    database: database.name,
    collection: "atestcollection",
    fields: [
        {
            fieldPath: "name",
            order: "ASCENDING",
        },
        {
            fieldPath: "description",
            order: "DESCENDING",
        },
    ],
});
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_DISABLED",
    deletion_policy="DELETE")
my_index = gcp.firestore.Index("my-index",
    project="my-project-name",
    database=database.name,
    collection="atestcollection",
    fields=[
        {
            "field_path": "name",
            "order": "ASCENDING",
        },
        {
            "field_path": "description",
            "order": "DESCENDING",
        },
    ])
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_DISABLED"),
			DeletionPolicy:        pulumi.String("DELETE"),
		})
		if err != nil {
			return err
		}
		_, err = firestore.NewIndex(ctx, "my-index", &firestore.IndexArgs{
			Project:    pulumi.String("my-project-name"),
			Database:   database.Name,
			Collection: pulumi.String("atestcollection"),
			Fields: firestore.IndexFieldArray{
				&firestore.IndexFieldArgs{
					FieldPath: pulumi.String("name"),
					Order:     pulumi.String("ASCENDING"),
				},
				&firestore.IndexFieldArgs{
					FieldPath: pulumi.String("description"),
					Order:     pulumi.String("DESCENDING"),
				},
			},
		})
		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_DISABLED",
        DeletionPolicy = "DELETE",
    });

    var my_index = new Gcp.Firestore.Index("my-index", new()
    {
        Project = "my-project-name",
        Database = database.Name,
        Collection = "atestcollection",
        Fields = new[]
        {
            new Gcp.Firestore.Inputs.IndexFieldArgs
            {
                FieldPath = "name",
                Order = "ASCENDING",
            },
            new Gcp.Firestore.Inputs.IndexFieldArgs
            {
                FieldPath = "description",
                Order = "DESCENDING",
            },
        },
    });

});
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.Index;
import com.pulumi.gcp.firestore.IndexArgs;
import com.pulumi.gcp.firestore.inputs.IndexFieldArgs;
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_DISABLED")
            .deletionPolicy("DELETE")
            .build());

        var my_index = new Index("my-index", IndexArgs.builder()
            .project("my-project-name")
            .database(database.name())
            .collection("atestcollection")
            .fields(            
                IndexFieldArgs.builder()
                    .fieldPath("name")
                    .order("ASCENDING")
                    .build(),
                IndexFieldArgs.builder()
                    .fieldPath("description")
                    .order("DESCENDING")
                    .build())
            .build());

    }
}
resources:
  database:
    type: gcp:firestore:Database
    properties:
      project: my-project-name
      name: database-id
      locationId: nam5
      type: FIRESTORE_NATIVE
      deleteProtectionState: DELETE_PROTECTION_DISABLED
      deletionPolicy: DELETE
  my-index:
    type: gcp:firestore:Index
    properties:
      project: my-project-name
      database: ${database.name}
      collection: atestcollection
      fields:
        - fieldPath: name
          order: ASCENDING
        - fieldPath: description
          order: DESCENDING

When you query documents filtering on both name and description, Firestore uses this index to avoid scanning the entire collection. The fields array defines which document properties to index; each field specifies a fieldPath and an order (ASCENDING or DESCENDING). Firestore automatically appends name as the final field if you don’t specify it explicitly.

Configure indexes for Datastore Mode databases

Applications using Datastore Mode need indexes with specific scope and API settings to support ancestor queries.

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-dm",
    locationId: "nam5",
    type: "DATASTORE_MODE",
    deleteProtectionState: "DELETE_PROTECTION_DISABLED",
    deletionPolicy: "DELETE",
});
const my_index = new gcp.firestore.Index("my-index", {
    project: "my-project-name",
    database: database.name,
    collection: "atestcollection",
    queryScope: "COLLECTION_RECURSIVE",
    apiScope: "DATASTORE_MODE_API",
    density: "SPARSE_ALL",
    fields: [
        {
            fieldPath: "name",
            order: "ASCENDING",
        },
        {
            fieldPath: "description",
            order: "DESCENDING",
        },
    ],
});
import pulumi
import pulumi_gcp as gcp

database = gcp.firestore.Database("database",
    project="my-project-name",
    name="database-id-dm",
    location_id="nam5",
    type="DATASTORE_MODE",
    delete_protection_state="DELETE_PROTECTION_DISABLED",
    deletion_policy="DELETE")
my_index = gcp.firestore.Index("my-index",
    project="my-project-name",
    database=database.name,
    collection="atestcollection",
    query_scope="COLLECTION_RECURSIVE",
    api_scope="DATASTORE_MODE_API",
    density="SPARSE_ALL",
    fields=[
        {
            "field_path": "name",
            "order": "ASCENDING",
        },
        {
            "field_path": "description",
            "order": "DESCENDING",
        },
    ])
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-dm"),
			LocationId:            pulumi.String("nam5"),
			Type:                  pulumi.String("DATASTORE_MODE"),
			DeleteProtectionState: pulumi.String("DELETE_PROTECTION_DISABLED"),
			DeletionPolicy:        pulumi.String("DELETE"),
		})
		if err != nil {
			return err
		}
		_, err = firestore.NewIndex(ctx, "my-index", &firestore.IndexArgs{
			Project:    pulumi.String("my-project-name"),
			Database:   database.Name,
			Collection: pulumi.String("atestcollection"),
			QueryScope: pulumi.String("COLLECTION_RECURSIVE"),
			ApiScope:   pulumi.String("DATASTORE_MODE_API"),
			Density:    pulumi.String("SPARSE_ALL"),
			Fields: firestore.IndexFieldArray{
				&firestore.IndexFieldArgs{
					FieldPath: pulumi.String("name"),
					Order:     pulumi.String("ASCENDING"),
				},
				&firestore.IndexFieldArgs{
					FieldPath: pulumi.String("description"),
					Order:     pulumi.String("DESCENDING"),
				},
			},
		})
		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-dm",
        LocationId = "nam5",
        Type = "DATASTORE_MODE",
        DeleteProtectionState = "DELETE_PROTECTION_DISABLED",
        DeletionPolicy = "DELETE",
    });

    var my_index = new Gcp.Firestore.Index("my-index", new()
    {
        Project = "my-project-name",
        Database = database.Name,
        Collection = "atestcollection",
        QueryScope = "COLLECTION_RECURSIVE",
        ApiScope = "DATASTORE_MODE_API",
        Density = "SPARSE_ALL",
        Fields = new[]
        {
            new Gcp.Firestore.Inputs.IndexFieldArgs
            {
                FieldPath = "name",
                Order = "ASCENDING",
            },
            new Gcp.Firestore.Inputs.IndexFieldArgs
            {
                FieldPath = "description",
                Order = "DESCENDING",
            },
        },
    });

});
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.Index;
import com.pulumi.gcp.firestore.IndexArgs;
import com.pulumi.gcp.firestore.inputs.IndexFieldArgs;
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-dm")
            .locationId("nam5")
            .type("DATASTORE_MODE")
            .deleteProtectionState("DELETE_PROTECTION_DISABLED")
            .deletionPolicy("DELETE")
            .build());

        var my_index = new Index("my-index", IndexArgs.builder()
            .project("my-project-name")
            .database(database.name())
            .collection("atestcollection")
            .queryScope("COLLECTION_RECURSIVE")
            .apiScope("DATASTORE_MODE_API")
            .density("SPARSE_ALL")
            .fields(            
                IndexFieldArgs.builder()
                    .fieldPath("name")
                    .order("ASCENDING")
                    .build(),
                IndexFieldArgs.builder()
                    .fieldPath("description")
                    .order("DESCENDING")
                    .build())
            .build());

    }
}
resources:
  database:
    type: gcp:firestore:Database
    properties:
      project: my-project-name
      name: database-id-dm
      locationId: nam5
      type: DATASTORE_MODE
      deleteProtectionState: DELETE_PROTECTION_DISABLED
      deletionPolicy: DELETE
  my-index:
    type: gcp:firestore:Index
    properties:
      project: my-project-name
      database: ${database.name}
      collection: atestcollection
      queryScope: COLLECTION_RECURSIVE
      apiScope: DATASTORE_MODE_API
      density: SPARSE_ALL
      fields:
        - fieldPath: name
          order: ASCENDING
        - fieldPath: description
          order: DESCENDING

The queryScope property set to COLLECTION_RECURSIVE enables queries across subcollections, while apiScope set to DATASTORE_MODE_API ensures compatibility with Datastore operations. The density property controls how the index handles missing fields; SPARSE_ALL means documents without all indexed fields are excluded from the index.

Machine learning applications store vector embeddings in Firestore and need indexes for nearest-neighbor searches.

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-vector",
    locationId: "nam5",
    type: "FIRESTORE_NATIVE",
    deleteProtectionState: "DELETE_PROTECTION_DISABLED",
    deletionPolicy: "DELETE",
});
const my_index = new gcp.firestore.Index("my-index", {
    project: "my-project-name",
    database: database.name,
    collection: "atestcollection",
    fields: [
        {
            fieldPath: "field_name",
            order: "ASCENDING",
        },
        {
            fieldPath: "__name__",
            order: "ASCENDING",
        },
        {
            fieldPath: "description",
            vectorConfig: {
                dimension: 128,
                flat: {},
            },
        },
    ],
});
import pulumi
import pulumi_gcp as gcp

database = gcp.firestore.Database("database",
    project="my-project-name",
    name="database-id-vector",
    location_id="nam5",
    type="FIRESTORE_NATIVE",
    delete_protection_state="DELETE_PROTECTION_DISABLED",
    deletion_policy="DELETE")
my_index = gcp.firestore.Index("my-index",
    project="my-project-name",
    database=database.name,
    collection="atestcollection",
    fields=[
        {
            "field_path": "field_name",
            "order": "ASCENDING",
        },
        {
            "field_path": "__name__",
            "order": "ASCENDING",
        },
        {
            "field_path": "description",
            "vector_config": {
                "dimension": 128,
                "flat": {},
            },
        },
    ])
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-vector"),
			LocationId:            pulumi.String("nam5"),
			Type:                  pulumi.String("FIRESTORE_NATIVE"),
			DeleteProtectionState: pulumi.String("DELETE_PROTECTION_DISABLED"),
			DeletionPolicy:        pulumi.String("DELETE"),
		})
		if err != nil {
			return err
		}
		_, err = firestore.NewIndex(ctx, "my-index", &firestore.IndexArgs{
			Project:    pulumi.String("my-project-name"),
			Database:   database.Name,
			Collection: pulumi.String("atestcollection"),
			Fields: firestore.IndexFieldArray{
				&firestore.IndexFieldArgs{
					FieldPath: pulumi.String("field_name"),
					Order:     pulumi.String("ASCENDING"),
				},
				&firestore.IndexFieldArgs{
					FieldPath: pulumi.String("__name__"),
					Order:     pulumi.String("ASCENDING"),
				},
				&firestore.IndexFieldArgs{
					FieldPath: pulumi.String("description"),
					VectorConfig: &firestore.IndexFieldVectorConfigArgs{
						Dimension: pulumi.Int(128),
						Flat:      &firestore.IndexFieldVectorConfigFlatArgs{},
					},
				},
			},
		})
		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-vector",
        LocationId = "nam5",
        Type = "FIRESTORE_NATIVE",
        DeleteProtectionState = "DELETE_PROTECTION_DISABLED",
        DeletionPolicy = "DELETE",
    });

    var my_index = new Gcp.Firestore.Index("my-index", new()
    {
        Project = "my-project-name",
        Database = database.Name,
        Collection = "atestcollection",
        Fields = new[]
        {
            new Gcp.Firestore.Inputs.IndexFieldArgs
            {
                FieldPath = "field_name",
                Order = "ASCENDING",
            },
            new Gcp.Firestore.Inputs.IndexFieldArgs
            {
                FieldPath = "__name__",
                Order = "ASCENDING",
            },
            new Gcp.Firestore.Inputs.IndexFieldArgs
            {
                FieldPath = "description",
                VectorConfig = new Gcp.Firestore.Inputs.IndexFieldVectorConfigArgs
                {
                    Dimension = 128,
                    Flat = 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.Index;
import com.pulumi.gcp.firestore.IndexArgs;
import com.pulumi.gcp.firestore.inputs.IndexFieldArgs;
import com.pulumi.gcp.firestore.inputs.IndexFieldVectorConfigArgs;
import com.pulumi.gcp.firestore.inputs.IndexFieldVectorConfigFlatArgs;
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-vector")
            .locationId("nam5")
            .type("FIRESTORE_NATIVE")
            .deleteProtectionState("DELETE_PROTECTION_DISABLED")
            .deletionPolicy("DELETE")
            .build());

        var my_index = new Index("my-index", IndexArgs.builder()
            .project("my-project-name")
            .database(database.name())
            .collection("atestcollection")
            .fields(            
                IndexFieldArgs.builder()
                    .fieldPath("field_name")
                    .order("ASCENDING")
                    .build(),
                IndexFieldArgs.builder()
                    .fieldPath("__name__")
                    .order("ASCENDING")
                    .build(),
                IndexFieldArgs.builder()
                    .fieldPath("description")
                    .vectorConfig(IndexFieldVectorConfigArgs.builder()
                        .dimension(128)
                        .flat(IndexFieldVectorConfigFlatArgs.builder()
                            .build())
                        .build())
                    .build())
            .build());

    }
}
resources:
  database:
    type: gcp:firestore:Database
    properties:
      project: my-project-name
      name: database-id-vector
      locationId: nam5
      type: FIRESTORE_NATIVE
      deleteProtectionState: DELETE_PROTECTION_DISABLED
      deletionPolicy: DELETE
  my-index:
    type: gcp:firestore:Index
    properties:
      project: my-project-name
      database: ${database.name}
      collection: atestcollection
      fields:
        - fieldPath: field_name
          order: ASCENDING
        - fieldPath: __name__
          order: ASCENDING
        - fieldPath: description
          vectorConfig:
            dimension: 128
            flat: {}

The vectorConfig property defines how vector fields are indexed. The dimension property specifies the embedding size (128 dimensions here), and the flat configuration uses a brute-force search algorithm. You can combine vector fields with traditional ordered fields, enabling queries that filter by metadata and then perform similarity search. The name field ensures deterministic ordering when multiple documents have similar vector distances.

Support MongoDB-compatible queries with multikey indexes

Applications using MongoDB-compatible APIs need indexes configured for array traversal and collection group queries.

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-mongodb-compatible",
    locationId: "nam5",
    type: "FIRESTORE_NATIVE",
    databaseEdition: "ENTERPRISE",
    deleteProtectionState: "DELETE_PROTECTION_DISABLED",
    deletionPolicy: "DELETE",
});
const my_index = new gcp.firestore.Index("my-index", {
    project: "my-project-name",
    database: database.name,
    collection: "atestcollection",
    apiScope: "MONGODB_COMPATIBLE_API",
    queryScope: "COLLECTION_GROUP",
    multikey: true,
    density: "DENSE",
    fields: [
        {
            fieldPath: "name",
            order: "ASCENDING",
        },
        {
            fieldPath: "description",
            order: "DESCENDING",
        },
    ],
});
import pulumi
import pulumi_gcp as gcp

database = gcp.firestore.Database("database",
    project="my-project-name",
    name="database-id-mongodb-compatible",
    location_id="nam5",
    type="FIRESTORE_NATIVE",
    database_edition="ENTERPRISE",
    delete_protection_state="DELETE_PROTECTION_DISABLED",
    deletion_policy="DELETE")
my_index = gcp.firestore.Index("my-index",
    project="my-project-name",
    database=database.name,
    collection="atestcollection",
    api_scope="MONGODB_COMPATIBLE_API",
    query_scope="COLLECTION_GROUP",
    multikey=True,
    density="DENSE",
    fields=[
        {
            "field_path": "name",
            "order": "ASCENDING",
        },
        {
            "field_path": "description",
            "order": "DESCENDING",
        },
    ])
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-mongodb-compatible"),
			LocationId:            pulumi.String("nam5"),
			Type:                  pulumi.String("FIRESTORE_NATIVE"),
			DatabaseEdition:       pulumi.String("ENTERPRISE"),
			DeleteProtectionState: pulumi.String("DELETE_PROTECTION_DISABLED"),
			DeletionPolicy:        pulumi.String("DELETE"),
		})
		if err != nil {
			return err
		}
		_, err = firestore.NewIndex(ctx, "my-index", &firestore.IndexArgs{
			Project:    pulumi.String("my-project-name"),
			Database:   database.Name,
			Collection: pulumi.String("atestcollection"),
			ApiScope:   pulumi.String("MONGODB_COMPATIBLE_API"),
			QueryScope: pulumi.String("COLLECTION_GROUP"),
			Multikey:   pulumi.Bool(true),
			Density:    pulumi.String("DENSE"),
			Fields: firestore.IndexFieldArray{
				&firestore.IndexFieldArgs{
					FieldPath: pulumi.String("name"),
					Order:     pulumi.String("ASCENDING"),
				},
				&firestore.IndexFieldArgs{
					FieldPath: pulumi.String("description"),
					Order:     pulumi.String("DESCENDING"),
				},
			},
		})
		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-mongodb-compatible",
        LocationId = "nam5",
        Type = "FIRESTORE_NATIVE",
        DatabaseEdition = "ENTERPRISE",
        DeleteProtectionState = "DELETE_PROTECTION_DISABLED",
        DeletionPolicy = "DELETE",
    });

    var my_index = new Gcp.Firestore.Index("my-index", new()
    {
        Project = "my-project-name",
        Database = database.Name,
        Collection = "atestcollection",
        ApiScope = "MONGODB_COMPATIBLE_API",
        QueryScope = "COLLECTION_GROUP",
        Multikey = true,
        Density = "DENSE",
        Fields = new[]
        {
            new Gcp.Firestore.Inputs.IndexFieldArgs
            {
                FieldPath = "name",
                Order = "ASCENDING",
            },
            new Gcp.Firestore.Inputs.IndexFieldArgs
            {
                FieldPath = "description",
                Order = "DESCENDING",
            },
        },
    });

});
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.Index;
import com.pulumi.gcp.firestore.IndexArgs;
import com.pulumi.gcp.firestore.inputs.IndexFieldArgs;
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-mongodb-compatible")
            .locationId("nam5")
            .type("FIRESTORE_NATIVE")
            .databaseEdition("ENTERPRISE")
            .deleteProtectionState("DELETE_PROTECTION_DISABLED")
            .deletionPolicy("DELETE")
            .build());

        var my_index = new Index("my-index", IndexArgs.builder()
            .project("my-project-name")
            .database(database.name())
            .collection("atestcollection")
            .apiScope("MONGODB_COMPATIBLE_API")
            .queryScope("COLLECTION_GROUP")
            .multikey(true)
            .density("DENSE")
            .fields(            
                IndexFieldArgs.builder()
                    .fieldPath("name")
                    .order("ASCENDING")
                    .build(),
                IndexFieldArgs.builder()
                    .fieldPath("description")
                    .order("DESCENDING")
                    .build())
            .build());

    }
}
resources:
  database:
    type: gcp:firestore:Database
    properties:
      project: my-project-name
      name: database-id-mongodb-compatible
      locationId: nam5
      type: FIRESTORE_NATIVE
      databaseEdition: ENTERPRISE
      deleteProtectionState: DELETE_PROTECTION_DISABLED
      deletionPolicy: DELETE
  my-index:
    type: gcp:firestore:Index
    properties:
      project: my-project-name
      database: ${database.name}
      collection: atestcollection
      apiScope: MONGODB_COMPATIBLE_API
      queryScope: COLLECTION_GROUP
      multikey: true
      density: DENSE
      fields:
        - fieldPath: name
          order: ASCENDING
        - fieldPath: description
          order: DESCENDING

The apiScope set to MONGODB_COMPATIBLE_API enables MongoDB-style query syntax, while multikey set to true allows the index to traverse array fields. The queryScope set to COLLECTION_GROUP means the index applies to all collections with this name across the database hierarchy. The density property set to DENSE includes documents even if they’re missing some indexed fields.

Enforce uniqueness constraints on indexed fields

Some applications need to guarantee that field combinations remain unique across documents.

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-unique",
    locationId: "nam5",
    type: "FIRESTORE_NATIVE",
    databaseEdition: "ENTERPRISE",
    deleteProtectionState: "DELETE_PROTECTION_DISABLED",
    deletionPolicy: "DELETE",
});
const my_index = new gcp.firestore.Index("my-index", {
    project: "my-project-name",
    database: database.name,
    collection: "atestcollection",
    apiScope: "MONGODB_COMPATIBLE_API",
    queryScope: "COLLECTION_GROUP",
    multikey: true,
    density: "DENSE",
    unique: true,
    fields: [
        {
            fieldPath: "name",
            order: "ASCENDING",
        },
        {
            fieldPath: "description",
            order: "DESCENDING",
        },
    ],
});
import pulumi
import pulumi_gcp as gcp

database = gcp.firestore.Database("database",
    project="my-project-name",
    name="database-id-unique",
    location_id="nam5",
    type="FIRESTORE_NATIVE",
    database_edition="ENTERPRISE",
    delete_protection_state="DELETE_PROTECTION_DISABLED",
    deletion_policy="DELETE")
my_index = gcp.firestore.Index("my-index",
    project="my-project-name",
    database=database.name,
    collection="atestcollection",
    api_scope="MONGODB_COMPATIBLE_API",
    query_scope="COLLECTION_GROUP",
    multikey=True,
    density="DENSE",
    unique=True,
    fields=[
        {
            "field_path": "name",
            "order": "ASCENDING",
        },
        {
            "field_path": "description",
            "order": "DESCENDING",
        },
    ])
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-unique"),
			LocationId:            pulumi.String("nam5"),
			Type:                  pulumi.String("FIRESTORE_NATIVE"),
			DatabaseEdition:       pulumi.String("ENTERPRISE"),
			DeleteProtectionState: pulumi.String("DELETE_PROTECTION_DISABLED"),
			DeletionPolicy:        pulumi.String("DELETE"),
		})
		if err != nil {
			return err
		}
		_, err = firestore.NewIndex(ctx, "my-index", &firestore.IndexArgs{
			Project:    pulumi.String("my-project-name"),
			Database:   database.Name,
			Collection: pulumi.String("atestcollection"),
			ApiScope:   pulumi.String("MONGODB_COMPATIBLE_API"),
			QueryScope: pulumi.String("COLLECTION_GROUP"),
			Multikey:   pulumi.Bool(true),
			Density:    pulumi.String("DENSE"),
			Unique:     pulumi.Bool(true),
			Fields: firestore.IndexFieldArray{
				&firestore.IndexFieldArgs{
					FieldPath: pulumi.String("name"),
					Order:     pulumi.String("ASCENDING"),
				},
				&firestore.IndexFieldArgs{
					FieldPath: pulumi.String("description"),
					Order:     pulumi.String("DESCENDING"),
				},
			},
		})
		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-unique",
        LocationId = "nam5",
        Type = "FIRESTORE_NATIVE",
        DatabaseEdition = "ENTERPRISE",
        DeleteProtectionState = "DELETE_PROTECTION_DISABLED",
        DeletionPolicy = "DELETE",
    });

    var my_index = new Gcp.Firestore.Index("my-index", new()
    {
        Project = "my-project-name",
        Database = database.Name,
        Collection = "atestcollection",
        ApiScope = "MONGODB_COMPATIBLE_API",
        QueryScope = "COLLECTION_GROUP",
        Multikey = true,
        Density = "DENSE",
        Unique = true,
        Fields = new[]
        {
            new Gcp.Firestore.Inputs.IndexFieldArgs
            {
                FieldPath = "name",
                Order = "ASCENDING",
            },
            new Gcp.Firestore.Inputs.IndexFieldArgs
            {
                FieldPath = "description",
                Order = "DESCENDING",
            },
        },
    });

});
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.Index;
import com.pulumi.gcp.firestore.IndexArgs;
import com.pulumi.gcp.firestore.inputs.IndexFieldArgs;
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-unique")
            .locationId("nam5")
            .type("FIRESTORE_NATIVE")
            .databaseEdition("ENTERPRISE")
            .deleteProtectionState("DELETE_PROTECTION_DISABLED")
            .deletionPolicy("DELETE")
            .build());

        var my_index = new Index("my-index", IndexArgs.builder()
            .project("my-project-name")
            .database(database.name())
            .collection("atestcollection")
            .apiScope("MONGODB_COMPATIBLE_API")
            .queryScope("COLLECTION_GROUP")
            .multikey(true)
            .density("DENSE")
            .unique(true)
            .fields(            
                IndexFieldArgs.builder()
                    .fieldPath("name")
                    .order("ASCENDING")
                    .build(),
                IndexFieldArgs.builder()
                    .fieldPath("description")
                    .order("DESCENDING")
                    .build())
            .build());

    }
}
resources:
  database:
    type: gcp:firestore:Database
    properties:
      project: my-project-name
      name: database-id-unique
      locationId: nam5
      type: FIRESTORE_NATIVE
      databaseEdition: ENTERPRISE
      deleteProtectionState: DELETE_PROTECTION_DISABLED
      deletionPolicy: DELETE
  my-index:
    type: gcp:firestore:Index
    properties:
      project: my-project-name
      database: ${database.name}
      collection: atestcollection
      apiScope: MONGODB_COMPATIBLE_API
      queryScope: COLLECTION_GROUP
      multikey: true
      density: DENSE
      unique: true
      fields:
        - fieldPath: name
          order: ASCENDING
        - fieldPath: description
          order: DESCENDING

The unique property set to true enforces that no two documents can have the same combination of indexed field values. This works like a database unique constraint, causing writes to fail if they would create duplicates. Unique indexes require MongoDB-compatible API scope and work with multikey indexes to handle array fields.

Beyond these examples

These snippets focus on specific index-level features: composite field indexing with sort orders, Datastore Mode and MongoDB-compatible configurations, and vector embeddings and uniqueness constraints. They’re intentionally minimal rather than full query optimization strategies.

The examples may reference pre-existing infrastructure such as Firestore databases (created inline or referenced) and GCP projects with Firestore enabled. They focus on configuring the index rather than provisioning the entire database infrastructure.

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

  • Single-field indexes (use gcp.firestore.Field instead)
  • Index deletion protection (deletionPolicy property)
  • Asynchronous index creation (skipWait property)
  • Sparse density variants (SPARSE_ALL vs SPARSE_ANY)

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

Let's configure GCP Firestore Indexes

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 an index?
Yes, you must have an existing Firestore database in your project. Create a gcp.firestore.Database resource first, or use gcp.appengine.Application if you’re using App Engine.
What's the difference between this resource and single field indexes?
This resource manages composite indexes only. For single field indexes, use the gcp.firestore.Field resource instead.
Index Configuration
Why is the __name__ field being added to my index automatically?
Firestore automatically appends __name__ as the last field if you don’t specify it. If the final field isn’t directional, __name__ will be ordered ASCENDING by default. You can explicitly specify __name__ to control its direction.
What's the difference between the density options?
Firestore supports three density configurations: SPARSE_ALL (all fields must be present), SPARSE_ANY (at least one field must be present), and DENSE (optimized for queries where all fields are typically present).
What's the difference between queryScope and apiScope?
queryScope controls where queries run: COLLECTION (single collection), COLLECTION_GROUP (all collections with same ID), or COLLECTION_RECURSIVE (collection and subcollections). apiScope controls which API can use the index: ANY_API, DATASTORE_MODE_API, or MONGODB_COMPATIBLE_API.
How do I create a vector index for similarity search?
Add a field with vectorConfig containing dimension (vector size) and flat (empty object for flat index type), as shown in the Vector example.
Immutability & Lifecycle
What properties can't be changed after creating the index?
Most properties are immutable: collection, fields, density, apiScope, queryScope, multikey, unique, database, and project. Changes to these require recreating the index.
What happens if I set deletionPolicy to PREVENT?
Setting deletionPolicy to PREVENT will block index deletion and cause terraform destroy to fail. Use DELETE (the default) for normal lifecycle management.
What does skipWait do?
Setting skipWait to true allows Terraform to continue without waiting for the index creation to complete. Useful for large indexes that take time to build.
Advanced Features
When should I use MONGODB_COMPATIBLE_API scope?
Use MONGODB_COMPATIBLE_API when you need MongoDB-compatible features like multikey indexes (indexes that traverse arrays). Set apiScope to MONGODB_COMPATIBLE_API and configure multikey as needed.
What does the multikey property do?
multikey allows the index to traverse arrays in document paths. For multikey indexes, at most one path can reach or traverse an array. This property only applies to indexes with MONGODB_COMPATIBLE_API scope.
Can I enforce uniqueness on indexed fields?
Yes, set unique to true to ensure all values for the indexed fields are unique across documents. This is a required, immutable property.

Using a different cloud?

Explore database guides for other cloud providers: