Provisions a Dialogflow CX intent (
google_dialogflow_cx_intent) — training phrases, parameter annotations, priority, and default-intent flags — targetinghashicorp/google ~> 7.0on Terraform>= 1.12.0.
- 🎯 Manages one Dialogflow CX intent (
google_dialogflow_cx_intent) — the natural-language matching unit that maps end-user utterances to a named intent within an agent. - 🗣️
training_phrases(with nestedparts) andparametersare both providernesting_mode = "list"blocks rendered inline viadynamic— no separate child resources. - 🏷️ The only Dialogflow CX resource confirmed in this batch to carry
labels/effective_labels/terraform_labels— every sibling module confirmed absent. - 🧯
deletion_policydefaults to"PREVENT"— a house extension over the provider's own"DELETE"default. ⚠️ is_default_negative_intent/is_default_welcome_intentare exclusivity-sensitive across module instances — Terraform cannot catch two intents on the same agent both claiming the flag.
💡 Why it matters: Intents are the primary decision surface for how a Dialogflow CX agent interprets member requests — priority, fallback wiring, and parameter annotation directly shape which conversational path a member's utterance takes. Route changes to production intent priority/fallback behavior through the team owning the member-support conversational design before applying.
If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:
- ⭐ Star this repository to help others discover this Terraform module.
- 🤝 Connect with me on LinkedIn: linkedin.com/in/microsoftexpert
- ☕ Buy me a coffee: buymeacoffee.com/microsoftexpert
Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!
graph LR
INTENT["🎯 terraform-google-dialogflow-cx-intent<br/>(this module)"] -->|parent = agent id| AGENT["🤖 terraform-google-dialogflow-cx-agent"]
ENTITY["🏷️ terraform-google-dialogflow-cx-entity-type<br/>(developer entity types)"] -.->|entity_type id| INTENT
INTENT -.->|intent id, by name| PAGE["📄 terraform-google-dialogflow-cx-page / -flow<br/>(intent routing)"]
style INTENT fill:#4285F4,color:#fff
style AGENT fill:#174EA6,color:#fff
style ENTITY fill:#e8eaed,color:#3c4043
style PAGE fill:#e8eaed,color:#3c4043
graph TD
P["parent, display_name"] --> R["google_dialogflow_cx_intent.this"]
T["training_phrases map"] -.dynamic.-> R
PM["parameters map"] -.dynamic.-> R
L["labels"] --> R
R --> O1["id"]
R --> O2["name"]
R --> O3["terraform_labels"]
R --> O4["effective_labels"]
style R fill:#4285F4,color:#fff
- One resource:
google_dialogflow_cx_intent.this. No child collection —training_phrasesandparametersare nested blocks on the same resource.
| Component | Constraint |
|---|---|
| Terraform | >= 1.12.0 |
hashicorp/google |
~> 7.0 |
| Provider block | None — the caller configures google |
Schema notes that bite:
parentis schema-Optional(omittable on import) but this module makes it a required variable — every intent belongs to exactly one agent.is_default_negative_intent/is_default_welcome_intentare confirmed plain optional booleans (notcomputed) — genuinely caller-settable, but the schema itself warns that more than one intent per agent with the same flagtruecauses the resources to compete for a single GCP resource.is_fallbackis effectively superseded byis_default_negative_intentper confirmed schema text — see Architecture Notes.labelsallows international/unicase letters and digits,-, and_, with a reservedsys-prefix (onlysys-head/sys-contextualpermitted) — enforced viavalidation, though the 128-byte-per-value cap is only proxied by a 63-character check (Terraform'slengthcounts characters, not UTF-8 bytes).training_phrases[*].partsrequires at least 1 item (confirmed schemamin_items: 1).- No
self_linkattribute — confirmed absent.
roles/dialogflow.admin(or a narrower Dialogflow configuration role) on the target project.
dialogflow.googleapis.comenabled.- The consuming agent must already exist; any
language_codesupplied here must already be enabled on that agent. - Developer entity types referenced by
parameters[*].entity_typemust already exist in the same agent.
terraform-google-dialogflow-cx-intent/
├── providers.tf
├── variables.tf
├── main.tf
├── outputs.tf
├── README.md
├── SCOPE.md
└── examples/basic/
module "cx_intent" {
source = "git::https://github.com/microsoftexpert/terraform-google-dialogflow-cx-intent.git?ref=v1.0.0"
parent = "projects/casey-prod-networking/locations/us-central1/agents/00000000-0000-0000-0000-000000000000"
display_name = "account.balance.inquiry"
training_phrases = {
balance_check = {
parts = [{ text = "What is my account balance?" }]
}
}
}Consumes
| Input | Type | Source module |
|---|---|---|
parent |
string, required |
terraform-google-dialogflow-cx-agent (.id) |
parameters[*].entity_type |
string, required per entry |
Caller literal (system entity type) or terraform-google-dialogflow-cx-entity-type (.id) |
Emits
| Output | Description | Consumed by |
|---|---|---|
id |
Terraform-internal id | None in this batch |
name |
Resource name (identical format to id) | None in this batch |
terraform_labels |
Labels this module manages plus provider defaults | None in this batch |
effective_labels |
Full label set GCP reports | None in this batch |
1 · Minimal — smallest valid call
module "cx_intent_minimal" {
source = "git::https://github.com/microsoftexpert/terraform-google-dialogflow-cx-intent.git?ref=v1.0.0"
parent = "projects/casey-prod-networking/locations/us-central1/agents/00000000-0000-0000-0000-000000000000"
display_name = "minimal.intent"
}💡 No training phrases, no parameters — a valid but practically unmatched intent shell.
2 · Single training phrase
module "cx_intent_single_phrase" {
source = "git::https://github.com/microsoftexpert/terraform-google-dialogflow-cx-intent.git?ref=v1.0.0"
parent = "projects/casey-prod-networking/locations/us-central1/agents/00000000-0000-0000-0000-000000000000"
display_name = "account.balance.inquiry"
training_phrases = {
balance_check = {
parts = [{ text = "What is my account balance?" }]
}
}
}3 · Multiple training phrases
module "cx_intent_multi_phrase" {
source = "git::https://github.com/microsoftexpert/terraform-google-dialogflow-cx-intent.git?ref=v1.0.0"
parent = "projects/casey-prod-networking/locations/us-central1/agents/00000000-0000-0000-0000-000000000000"
display_name = "loan.payment.due_date"
training_phrases = {
phrase_1 = { parts = [{ text = "When is my next loan payment due?" }] }
phrase_2 = { parts = [{ text = "What date is my payment due?" }] }
phrase_3 = { parts = [{ text = "Tell me my next due date." }], repeat_count = 2 }
}
}ℹ️
repeat_countreflects how many times an example was added to the intent — the map key is an arbitrary caller-chosen string sincetraining_phrases[*].idiscomputed-only.
4 · Training phrase annotated with a system entity type
module "cx_intent_system_entity" {
source = "git::https://github.com/microsoftexpert/terraform-google-dialogflow-cx-intent.git?ref=v1.0.0"
parent = "projects/casey-prod-networking/locations/us-central1/agents/00000000-0000-0000-0000-000000000000"
display_name = "statement.request.by_date"
parameters = {
statement_date = {
entity_type = "projects/-/locations/-/agents/-/entityTypes/sys.date"
}
}
training_phrases = {
phrase_1 = {
parts = [
{ text = "Send me my statement for " },
{ text = "last March", parameter_id = "statement_date" },
]
}
}
}💡 System entity types use the wildcard
projects/-/locations/-/agents/-/entityTypes/sys.<Name>form — noproject/location/agentsubstitution needed.
5 · Training phrase annotated with a developer entity type (is_list + redact)
module "cx_intent_developer_entity" {
source = "git::https://github.com/microsoftexpert/terraform-google-dialogflow-cx-intent.git?ref=v1.0.0"
parent = "projects/casey-prod-networking/locations/us-central1/agents/00000000-0000-0000-0000-000000000000"
display_name = "member.pin.verification"
parameters = {
account_pin = {
entity_type = module.cx_entity_type_pin.id
is_list = false
redact = true
}
}
training_phrases = {
phrase_1 = {
parts = [
{ text = "My PIN is " },
{ text = "1234", parameter_id = "account_pin" },
]
}
}
}🔒
redact = truereplaces the parameter content with the parameter name during logging — recommended for anything resembling a PIN, account number, or other sensitive fragment.
6 · Priority tuning — high-priority intent
module "cx_intent_high_priority" {
source = "git::https://github.com/microsoftexpert/terraform-google-dialogflow-cx-intent.git?ref=v1.0.0"
parent = "projects/casey-prod-networking/locations/us-central1/agents/00000000-0000-0000-0000-000000000000"
display_name = "fraud.report.urgent"
priority = 900000
training_phrases = {
phrase_1 = { parts = [{ text = "I think someone stole my card." }] }
}
}7 · Negative priority — intent excluded from runtime matching
module "cx_intent_disabled" {
source = "git::https://github.com/microsoftexpert/terraform-google-dialogflow-cx-intent.git?ref=v1.0.0"
parent = "projects/casey-prod-networking/locations/us-central1/agents/00000000-0000-0000-0000-000000000000"
display_name = "legacy.retired_flow"
priority = -1
training_phrases = {
phrase_1 = { parts = [{ text = "some retired phrasing" }] }
}
}
⚠️ Confirmed schema text: a negativeprioritymeans "the intent is ignored in runtime detect intent requests" — the intent still exists and can be trained/tested, but never matches live traffic. Useful for staging a retirement without a destructive delete.
8 · Non-default language code
module "cx_intent_spanish" {
source = "git::https://github.com/microsoftexpert/terraform-google-dialogflow-cx-intent.git?ref=v1.0.0"
parent = "projects/casey-prod-networking/locations/us-central1/agents/00000000-0000-0000-0000-000000000000"
display_name = "account.balance.inquiry.es"
language_code = "es"
training_phrases = {
phrase_1 = { parts = [{ text = "¿Cuál es el saldo de mi cuenta?" }] }
}
}ℹ️
esmust already be enabled as a supported language on the consuming agent — an API-level rejection at apply time, not caught byterraform validate.
9 · Labels applied
module "cx_intent_labeled" {
source = "git::https://github.com/microsoftexpert/terraform-google-dialogflow-cx-intent.git?ref=v1.0.0"
parent = "projects/casey-prod-networking/locations/us-central1/agents/00000000-0000-0000-0000-000000000000"
display_name = "account.balance.inquiry"
labels = {
environment = "prod"
team = "member-support"
sys-head = ""
}
training_phrases = {
phrase_1 = { parts = [{ text = "What is my account balance?" }] }
}
}🏷️
sys-headis one of the two Dialogflow-reserved label keys (the other issys-contextual) and is deliberately allowed an empty value — confirmed schema text: "The above labels do not require value."
10 · Reserved label prefix — documented failure mode
# FAILS terraform plan — cross-field validation rejects an unrecognized "sys-" key.
module "cx_intent_bad_label" {
source = "git::https://github.com/microsoftexpert/terraform-google-dialogflow-cx-intent.git?ref=v1.0.0"
parent = "projects/casey-prod-networking/locations/us-central1/agents/00000000-0000-0000-0000-000000000000"
display_name = "invalid-label-demo"
labels = {
sys-custom = "not-allowed"
}
}
⚠️ Demonstrates the plan-time guard — onlysys-headandsys-contextualare permitted with thesys-prefix (confirmed schema text); this never reaches the API.
11 · Default Welcome Intent
module "cx_intent_welcome" {
source = "git::https://github.com/microsoftexpert/terraform-google-dialogflow-cx-intent.git?ref=v1.0.0"
parent = "projects/casey-prod-networking/locations/us-central1/agents/00000000-0000-0000-0000-000000000000"
display_name = "Default Welcome Intent"
is_default_welcome_intent = true
training_phrases = {
phrase_1 = { parts = [{ text = "hi" }] }
phrase_2 = { parts = [{ text = "hello" }] }
}
}
⚠️ Ensure at most ONEterraform-google-dialogflow-cx-intentinstance per agent setsis_default_welcome_intent = true— Terraform cannot detect a second instance elsewhere in the composition claiming the same flag.
12 · Default Negative Intent (fallback management)
module "cx_intent_negative" {
source = "git::https://github.com/microsoftexpert/terraform-google-dialogflow-cx-intent.git?ref=v1.0.0"
parent = "projects/casey-prod-networking/locations/us-central1/agents/00000000-0000-0000-0000-000000000000"
display_name = "Default Negative Intent"
is_default_negative_intent = true
training_phrases = {
phrase_1 = { parts = [{ text = "asdkjfh nonsense input" }] }
}
}💡 Confirmed schema text: the supported way to manage the agent's fallback intent is
is_default_negative_intent = true, notis_fallbackdirectly.
13 · Custom deletion policy — allow delete in dev
module "cx_intent_dev" {
source = "git::https://github.com/microsoftexpert/terraform-google-dialogflow-cx-intent.git?ref=v1.0.0"
parent = "projects/casey-dev-networking/locations/us-central1/agents/00000000-0000-0000-0000-000000000000"
display_name = "dev.scratch.intent"
deletion_policy = "DELETE"
training_phrases = {
phrase_1 = { parts = [{ text = "test phrase" }] }
}
}14 · Explicit timeouts
module "cx_intent_timeouts" {
source = "git::https://github.com/microsoftexpert/terraform-google-dialogflow-cx-intent.git?ref=v1.0.0"
parent = "projects/casey-prod-networking/locations/us-central1/agents/00000000-0000-0000-0000-000000000000"
display_name = "custom-timeouts-demo"
timeouts = {
create = "10m"
update = "10m"
delete = "10m"
}
training_phrases = {
phrase_1 = { parts = [{ text = "example phrase" }] }
}
}15 · 🏗️ End-to-end composition
module "cx_agent" {
source = "git::https://github.com/microsoftexpert/terraform-google-dialogflow-cx-agent.git?ref=v1.0.0"
display_name = "Member Support Agent"
location = "us-central1"
default_language_code = "en"
time_zone = "America/Chicago"
}
module "cx_intent_balance_inquiry" {
source = "git::https://github.com/microsoftexpert/terraform-google-dialogflow-cx-intent.git?ref=v1.0.0"
parent = module.cx_agent.id
display_name = "account.balance.inquiry"
priority = 750000
labels = {
team = "member-support"
}
parameters = {
account_type = {
entity_type = "projects/-/locations/-/agents/-/entityTypes/sys.any"
}
}
training_phrases = {
phrase_1 = {
parts = [
{ text = "What is my " },
{ text = "checking", parameter_id = "account_type" },
{ text = " account balance?" },
]
}
phrase_2 = { parts = [{ text = "How much money do I have?" }] }
}
}
module "cx_intent_welcome" {
source = "git::https://github.com/microsoftexpert/terraform-google-dialogflow-cx-intent.git?ref=v1.0.0"
parent = module.cx_agent.id
display_name = "Default Welcome Intent"
is_default_welcome_intent = true
training_phrases = {
phrase_1 = { parts = [{ text = "hi" }] }
phrase_2 = { parts = [{ text = "hello" }] }
}
}| Variable | Type | Default | Notes |
|---|---|---|---|
parent |
string |
— required | Sourced from terraform-google-dialogflow-cx-agent's id |
display_name |
string |
— required | Unique within the agent |
description |
string |
null |
Max 140 characters |
language_code |
string |
null |
Must already be enabled on the agent |
priority |
number |
null |
Negative excludes from runtime matching |
is_default_negative_intent |
bool |
null |
Exclusivity-sensitive across module instances |
is_default_welcome_intent |
bool |
null |
Exclusivity-sensitive across module instances |
is_fallback |
bool |
null |
Superseded by is_default_negative_intent per schema text |
deletion_policy |
string |
"PREVENT" |
House extension; "DELETE" | "ABANDON" | "PREVENT" |
parameters |
map(object(...)) |
{} |
Keyed by the parameter's own id |
training_phrases |
map(object(...)) |
{} |
Keyed by an arbitrary caller-chosen string |
labels |
map(string) |
{} |
sys- prefix reserved except sys-head/sys-contextual |
timeouts |
object({ create, update, delete }) |
null |
Full object schemas
variable "parameters" {
type = map(object({
entity_type = string
is_list = optional(bool)
redact = optional(bool)
}))
default = {}
}
variable "training_phrases" {
type = map(object({
repeat_count = optional(number)
parts = list(object({
text = string
parameter_id = optional(string)
}))
}))
default = {}
}| Output | Description |
|---|---|
id |
Terraform-internal id (primary output) |
name |
Resource name (identical format to id) |
terraform_labels |
Labels this module manages plus provider-level default labels |
effective_labels |
Full label set GCP reports, including labels applied outside Terraform |
No self_link — confirmed absent.
parametersandtraining_phrasesare nested blocks on the singlethisresource, not separate child resources — thefor_each-over-a-keyed-map convention is followed for readable diffs, not to avoid resource re-indexing (there is none to avoid here).parametersreuses its own requiredidfield as the map key since that value is the exact stringtraining_phrases[*].parts[*].parameter_idmust match to annotate a training phrase.is_default_negative_intent/is_default_welcome_intentare confirmed genuine caller-settable booleans (notcomputed), but only onegoogle_dialogflow_cx_intentper agent should set each totrue— a cross-module-instance constraint Terraform's type system andvalidation {}blocks cannot express (they only see one instance's inputs at a time).is_fallbackis documented by the provider as effectively superseded byis_default_negative_intent— both are exposed here since the schema marks both plain optional booleans, but new compositions should preferis_default_negative_intent.labelscharacter-set validation permits Unicode letters (\p{L}) per confirmed schema text ("International characters are allowed, including letters from unicase alphabets") — this is deliberately looser than a pure-ASCII lowercase regex some other GCP resources'labelsuse.
| Concern | Secure default | Opt-out |
|---|---|---|
| Deletion protection | deletion_policy = "PREVENT" |
Caller sets "DELETE" or "ABANDON" |
| Sensitive parameter logging | redact left to caller per parameter (no house-wide default — provider default is false) |
Caller sets redact = true per parameter |
| Reserved label keys | sys- prefix rejected unless it is exactly sys-head/sys-contextual |
N/A — provider-reserved values, not a caller opt-out |
terraform init -backend=false
terraform validate
terraform fmt -checkPin ?ref=v1.0.0 — never a branch. Plan-only; a human applies from CI.
validate/fmt -check confirm structural correctness only, given the caller has valid ADC/WIF
credentials configured for any downstream plan. They cannot catch: language_code not enabled
on the agent, a developer entity_type that doesn't exist, or a second intent on the same agent
also claiming is_default_welcome_intent/is_default_negative_intent — all of these fail only at
apply time against a real project.
$ terraform output
id = "projects/casey-prod-networking/locations/us-central1/agents/00000000-0000-0000-0000-000000000000/intents/a1b2c3d4-0000-1111-2222-333344445555"
name = "projects/casey-prod-networking/locations/us-central1/agents/00000000-0000-0000-0000-000000000000/intents/a1b2c3d4-0000-1111-2222-333344445555"
terraform_labels = {
"team" = "member-support"
}
effective_labels = {
"team" = "member-support"
}
| Symptom | Cause | Fix |
|---|---|---|
apply fails with a language error |
language_code not enabled on the consuming agent |
Enable the language on the agent first, or omit to use the agent's default |
apply fails resolving entity_type |
Developer entity type doesn't exist yet, or wrong agent | Create the entity type first, in the same agent |
| Two intents both claim the welcome/negative flag | Multiple terraform-google-dialogflow-cx-intent instances set the same is_default_* flag true |
Set the flag on exactly one instance per agent |
plan rejects a labels key |
Key uses the reserved sys- prefix but isn't sys-head/sys-contextual |
Rename the key, or use the two permitted reserved keys |
plan rejects training_phrases entry |
A parts list is empty |
Supply at least one parts entry per training phrase |
google_dialogflow_cx_intentterraform-google-dialogflow-cx-agent,terraform-google-dialogflow-cx-entity-type- This module's
SCOPE.md

