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 FREEFrequently Asked Questions
Prerequisites & Setup
gcp.firestore.Database resource with locationId set before creating Field resources.Field Configuration & Immutability
collection, field, project, and database properties are all immutable and cannot be changed after creation.database property defaults to "(default)" if not specified.Index Management
indexConfig with an empty block (indexConfig: {}) to disable all indexes on the field.TTL Configuration
ttlConfig to an empty block (ttlConfig: {}). To disable TTL, leave ttlConfig unset or remove it when updating the resource.Advanced Features
field to "*" to create a wildcard configuration that applies to all fields in the collection group.