A Terraform provider for managing Google Forms as infrastructure, with optional Google Drive and Google Sheets helpers for common workflows (folder placement, permissions, response spreadsheets, etc.).
This provider is intentionally "typed-first, escape-hatch always":
- Use strongly-typed HCL for common Forms/Sheets/Drive operations.
- When Google adds API features faster than provider schema can evolve, use
*_batch_updateresources for full-power raw JSON requests.
| Area | Resource | Purpose |
|---|---|---|
| Forms | googleforms_form |
Typed Form + items (questions), quiz, publish/accept responses, Drive folder placement |
| Forms | googleforms_forms_batch_update |
Escape hatch for Forms forms.batchUpdate |
| Forms | googleforms_response_sheet |
Track/validate Form <-> Spreadsheet association |
| Sheets | googleforms_spreadsheet |
Spreadsheet + Drive folder placement |
| Sheets | googleforms_sheet |
Sheet/tab within a spreadsheet |
| Sheets | googleforms_sheet_values |
Bounded A1 range writes, optional read-back drift detection |
| Sheets | googleforms_sheets_batch_update |
Escape hatch for Sheets spreadsheets.batchUpdate |
| Sheets | googleforms_sheets_named_range |
Named ranges via batchUpdate |
| Sheets | googleforms_sheets_protected_range |
Protected ranges via batchUpdate |
| Sheets | googleforms_sheets_developer_metadata |
Developer metadata via batchUpdate |
| Sheets | googleforms_sheets_data_validation |
Data validation rules (JSON) via batchUpdate |
| Sheets | googleforms_sheets_conditional_format_rule |
Conditional format rule (JSON) addressed by index |
| Drive | googleforms_drive_folder |
Create/manage Drive folders |
| Drive | googleforms_drive_file |
Adopt existing file; rename/move; optional delete-on-destroy |
| Drive | googleforms_drive_permission |
Share Drive-backed documents |
| Area | Data source | Purpose |
|---|---|---|
| Forms | data.googleforms_form |
Read a Form by ID |
| Drive | data.googleforms_drive_file |
Read a Drive file by ID |
| Sheets | data.googleforms_spreadsheet |
Read a spreadsheet by ID |
| Sheets | data.googleforms_sheet_values |
Read sheet values for an A1 range |
- Provider docs:
docs/ - Resources:
docs/resources/ - Data sources:
docs/data-sources/ - Guide:
docs/guides/import-existing-form.md
- A Google Cloud project with the Forms API enabled
- A Google Cloud project with the Drive API enabled (required for folder placement, permissions, and some document operations)
- A Google Cloud project with the Sheets API enabled (required for spreadsheet resources)
- A service account with appropriate permissions
- Terraform >= 1.0
terraform {
required_providers {
googleforms = {
source = "45ck/googleforms"
version = "~> 0.1"
}
}
}
# Uses Application Default Credentials by default.
# Set GOOGLE_CREDENTIALS env var for service account JSON.
provider "googleforms" {}resource "googleforms_form" "survey" {
title = "Employee Satisfaction Survey"
description = "Managed by Terraform."
published = true
accepting_responses = true
update_strategy = "targeted" # avoids replace-all when only editing existing items
item {
item_key = "department"
multiple_choice {
question_text = "Which department are you in?"
options = ["Engineering", "Marketing", "Sales", "HR"]
required = true
}
}
item {
item_key = "name"
short_answer {
question_text = "What is your name?"
required = true
}
}
item {
item_key = "feedback"
paragraph {
question_text = "Any additional feedback?"
}
}
}
output "form_url" {
value = googleforms_form.survey.responder_uri
}resource "googleforms_form" "quiz" {
title = "Geography Quiz"
quiz = true
published = true
accepting_responses = true
item {
item_key = "q1_capital"
multiple_choice {
question_text = "What is the capital of France?"
options = ["Paris", "London", "Rome", "Berlin"]
required = true
grading {
points = 5
correct_answer = "Paris"
}
}
}
}For question types or settings not yet natively supported, use content_json:
resource "googleforms_form" "advanced" {
title = "Advanced Form"
published = true
content_json = jsonencode([
{
title = "Rate our service"
questionItem = {
question = {
scaleQuestion = {
low = 1
high = 5
lowLabel = "Poor"
highLabel = "Excellent"
}
}
}
}
])
}googleforms_form supports two complementary item modes:
- Typed items via
item { ... }blocks for supported question/item types. - Raw JSON items via
content_json(mutually exclusive withitemblocks).
When using typed item blocks:
manage_mode = "all": treat the configured item list as authoritative for the whole form.manage_mode = "partial": only manage the configured items (byitem_key) and leave other items untouched.
When changing items, choose an update strategy:
update_strategy = "targeted": uses FormsbatchUpdateto update/move/create/delete items correlated byitem_key+ storedgoogle_item_id. Safer for preserving response mappings and external integrations. Refuses question type changes.update_strategy = "replace_all": deletes/recreates items when changes occur. This can break response mappings/integrations; gated bydangerously_replace_all_items.
Branching forms (go-to-section behavior) are supported for choice options via option { ... }. When you reference sections by go_to_section_key, the provider resolves keys to IDs after item IDs exist.
The provider supports three authentication methods (in priority order):
credentialsattribute — Path to or content of a service account JSON keyGOOGLE_CREDENTIALSenvironment variable — Path to service account JSON- Application Default Credentials — Automatically detected from environment
Optional: impersonate_user for Google Workspace domain-wide delegation.
Required OAuth scopes: forms.body, drive.file, spreadsheets.
These are the top "surprises" users hit when automating Forms and Drive-backed docs:
- File upload questions: the Forms API does not support creating them via the same typed item workflows. The provider supports
file_uploadprimarily for imported/existing items and state. - Response destination linking: the Forms REST API does not support programmatically linking a Form to a response Spreadsheet.
googleforms_response_sheettracks and can validate the association, but cannot create it. revision_idwrite control: when usingconflict_policy = "fail", therevision_idis only valid for a limited time (Google currently documents ~24 hours). Plan/apply long after the last read may require a refresh.googleforms_sheets_conditional_format_ruleuses an index intoconditionalFormats. Out-of-band edits that insert/remove rules can shift indexes and cause unexpected diffs.googleforms_sheet_valuesis intentionally range-scoped to prevent state explosion. Manage large sheets as many small ranges (or usegoogleforms_sheets_batch_update).
terraform import googleforms_form.existing FORM_IDSee the import guide for details.
See examples/ for complete configurations:
- Forms:
examples/resources/google_forms_form/ - Sheets:
examples/resources/google_forms_spreadsheet/,examples/resources/google_forms_sheet/,examples/resources/google_forms_sheet_values/ - Escape hatches:
examples/resources/google_forms_forms_batch_update/,examples/resources/google_forms_sheets_batch_update/ - Drive:
examples/resources/google_forms_drive_folder/,examples/resources/google_forms_drive_permission/
# Install dependencies
go mod download
# Install pre-commit hooks
pre-commit install
# Build
make build
# Run all quality checks
make ci
# Run unit tests
make test
# Run acceptance tests (requires GOOGLE_CREDENTIALS)
make test-accWindows note: if go test fails with Access is denied due to temp exe execution restrictions, use make test-docker and make docs-docker.
See CONTRIBUTING.md for full development guide.
- Form responses export/management is not implemented yet (responses are usually treated as data, not infrastructure).
- Apps Script automation is not included in this provider core (service-account-only CI constraints and operational complexity).
Apache License 2.0. See LICENSE.