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 four capabilities: composite field indexing for compound queries, vector embeddings for similarity search, Datastore and MongoDB API compatibility, and uniqueness constraints.

Indexes require an existing Firestore database and target a specific collection. The examples show database creation inline, but you can reference existing databases. The examples are intentionally small. Combine them with your own database configuration and query patterns.

Index multiple fields for compound queries

Most Firestore applications query documents by multiple fields simultaneously, such as filtering by name and sorting by description.

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

The fields array defines which document properties participate in the index and their sort order. Each field specifies a fieldPath (the document property name) and an order (ASCENDING or DESCENDING). Firestore automatically appends __name__ as the final field if not explicitly included, ensuring deterministic query results.

Applications using machine learning embeddings store and query high-dimensional vectors for semantic search or recommendations.

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 enables vector similarity queries. The dimension property specifies the vector size (must match your embedding model’s output), and the flat configuration uses brute-force search. Vector fields must appear last in the fields array, after any ordering fields like __name__.

Configure indexes for Datastore Mode databases

Teams migrating from Cloud Datastore or using Datastore Mode need indexes that work with Datastore API semantics.

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 apiScope property set to DATASTORE_MODE_API restricts the index to Datastore queries. The queryScope property controls whether the index applies to a single collection (COLLECTION) or spans collection groups (COLLECTION_RECURSIVE). The density property (SPARSE_ALL, SPARSE_ANY, or DENSE) affects how Firestore handles documents with missing indexed fields.

Enable MongoDB-compatible API queries

Applications using Firestore’s MongoDB-compatible API need indexes configured for MongoDB query patterns.

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 property set to MONGODB_COMPATIBLE_API enables MongoDB query compatibility. The multikey property allows indexing array fields, and queryScope set to COLLECTION_GROUP enables queries across collection hierarchies. This configuration requires a Firestore database with ENTERPRISE edition.

Enforce uniqueness constraints across documents

Some applications need to guarantee that field combinations remain unique across all documents, similar to database unique constraints.

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 enforces uniqueness for the indexed field combination. When set to true, Firestore rejects writes that would create duplicate values. Unique indexes only work with MONGODB_COMPATIBLE_API scope and require ENTERPRISE edition databases.

Beyond these examples

These snippets focus on specific index-level features: composite field indexing, vector similarity search, Datastore and MongoDB API compatibility, and uniqueness constraints. They’re intentionally minimal rather than full query optimization strategies.

The examples reference pre-existing infrastructure such as Firestore databases (created inline or existing separately) 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 resource instead)
  • Index exemptions and query optimization strategies
  • TTL policies and index lifecycle management
  • Performance tuning (index size, query planning)

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

Setup & Prerequisites
Do I need to create a Firestore database before creating an index?
Yes, a Firestore database must exist on the project before creating an index. Create a gcp.firestore.Database resource first, or create a gcp.appengine.Application resource (the Firestore location will match the App Engine location).
What's the difference between composite and single field indexes?
This resource manages composite indexes only. For single field indexes, use the gcp.firestore.Field resource instead.
Index Configuration
What happens if I don't specify __name__ in my fields?
The __name__ field is automatically added with the same direction as the last field defined. If the final field isn’t directional, __name__ will be ordered ASCENDING.
What are the default values for optional properties?
The defaults are: database defaults to "(default)", apiScope defaults to ANY_API, and queryScope defaults to COLLECTION.
What density options are available?
Three density configurations are available: SPARSE_ALL, SPARSE_ANY, and DENSE. The density property is required and immutable.
What query scopes can I use?
Three query scopes are available: COLLECTION (default), COLLECTION_GROUP, and COLLECTION_RECURSIVE.
Immutability & Updates
What properties are immutable after index creation?
All core properties are immutable: collection, density, fields, project, unique, apiScope, database, multikey, and queryScope. You cannot modify an index after creation.
Advanced Features
Can I create vector indexes?
Yes, use vectorConfig within a field definition with dimension and flat properties.
How do I create a unique index?
Set unique: true in the index configuration. This ensures all values for indexed fields are unique across documents.
When should I use multikey indexes?
Use multikey: true when at most one path in the index definition reaches or traverses an array (except via explicit array index). This only applies to indexes with MONGODB_COMPATIBLE_API ApiScope.

Using a different cloud?

Explore database guides for other cloud providers: