Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.formal.ai/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The Formal Terraform Provider enables you to manage Connectors, Resources, Policies, Users, and all other Formal objects as infrastructure-as-code.

Installation

Add the provider to your Terraform configuration:
terraform {
  required_providers {
    formal = {
      source  = "formalco/formal"
      version = "~> 4.12.8"
    }
  }
}

provider "formal" {
  api_key = var.formal_api_key
}

Authentication

Create an API key in the Formal console:
  1. Navigate to API Keys
  2. Click Create API Key
  3. Name the key (e.g., “terraform-production”)
  4. Copy the key immediately
Set the key as an environment variable or Terraform variable:
export FORMAL_API_KEY="your-api-key-here"
Or in Terraform:
variable "formal_api_key" {
  description = "Formal API key"
  type        = string
  sensitive   = true
}

Examples

Full Production Stack

terraform {
  required_providers {
    formal = {
      source  = "formalco/formal"
      version = "~> 4.12.7"
    }
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.17.0"
    }
  }
}

# Variables
variable "name" {
  description = "Base name for resources"
  type        = string
  default     = "formal-production"
}

variable "datadog_application_key" {
  description = "Datadog Application Key"
  type        = string
  default     = "datadog-account-id"
  sensitive   = true
}

variable "datadog_api_key" {
  description = "Datadog API key"
  type        = string
  sensitive   = true
  default     = "datadog-api-key"
}

# Configure the Formal Provider
provider "formal" {
  api_key = "ENPxVb62qlm04BRU8Hkd7a5e31QzOW9X"
  retrieve_sensitive_values = false
}

# Configure AWS Provider
provider "aws" {
  region = "us-east-1"
}
# Create a Space
resource "formal_space" "production" {
  name = "production"
}

# Create a Resource
resource "formal_resource" "postgres" {
  name       = "production-postgres"
  technology = "postgres"
  hostname   = "prod-db.us-east-1.rds.amazonaws.com"
  port       = 5432
  space_id   = formal_space.production.id

  termination_protection = false
}

# Create a Connector
resource "formal_connector" "production" {
  name     = "production-connector"
  space_id = formal_space.production.id
}

# Configure Connector Settings
resource "formal_connector_configuration" "production" {
  connector_id = formal_connector.production.id
  log_level    = "info"
  # metrics collector endpoint. If there is no collector at the endpoint specified, the
  # the connector will not send metrics
  otel_endpoint_hostname = "localhost"
  otel_endpoint_port     = 4317
}

# Create Connector Listener
resource "formal_connector_listener" "production_postgres" {
  connector_id = formal_connector.production.id
  name         = "production-postgres-listener"
  port         = 5432
}

# Add Listener Rule for Resource
resource "formal_connector_listener_rule" "postgres" {
  connector_listener_id = formal_connector_listener.production_postgres.id
  type                  = "resource"
  rule                  = formal_resource.postgres.id
}

# Create Users
resource "formal_user" "alice" {
  first_name = "Alice"
  last_name  = "Smith"
  email      = "alice@example.com"
  type       = "human"
}

resource "formal_user" "looker_app" {
  name = "Looker Application"
  type = "machine"
}

# Create Groups
resource "formal_group" "engineering" {
  name        = "Engineering Team"
  description = "Engineers with database access"
}

resource "formal_group_user_link" "alice_eng" {
  group_id = formal_group.engineering.id
  user_id  = formal_user.alice.id
}

# Create a Policy
resource "formal_policy" "mask_pii" {
  name         = "mask-pii-data"
  description  = "Mask PII fields for non-privileged users"
  status       = "active"
  owner        = "admin@example.com"
  notification = "none"

  module = <<-EOT
    package formal.v2

    import future.keywords.if
    import future.keywords.in

    response := {
      "action": "mask",
      "type": "nullify",
      "columns": pii_columns
    } if {
      not "pii_access" in input.user.groups

      pii_columns := [col |
        col := input.row[_]
        col["data_label"] in ["email", "ssn", "phone"]
      ]

      count(pii_columns) > 0
    }
  EOT
}

# AWS S3 Bucket for Logs
resource "aws_s3_bucket" "logs" {
  bucket        = "${var.name}-logs"
  force_destroy = true
}

# AWS Integration
resource "formal_integration_cloud" "aws" {
  name         = "aws-production"
  cloud_region = "us-east-1"

  aws {
    template_version          = "latest"
    enable_rds_autodiscovery  = true
    enable_eks_autodiscovery  = true
    allow_s3_access           = true
    s3_bucket_arn             = "${aws_s3_bucket.logs.arn}/*"
  }
}

# Log Integration
resource "formal_integration_log" "datadog" {
  name = "datadog-logs"

  datadog {
    account_id = var.datadog_application_key
    api_key    = var.datadog_api_key
    site       = "datadoghq.com"
  }
}

Resource Documentation

Full documentation for all resources: Core Resources: Integrations: Data Sources:

Example Repositories

Formal provides complete Terraform examples: Clone and customize for your needs:
git clone https://github.com/formalco/terraform-provider-formal.git
cd terraform-provider-formal/examples/deployments/aws/connector

Best Practices

Pin provider versions to avoid unexpected changes:
formal = {
  source  = "formalco/formal"
  version = "~> 4.12.8"  # Allow patches, not minor versions
}
Use remote state backends (S3, Terraform Cloud) for team collaboration:
terraform {
  backend "s3" {
    bucket = "my-terraform-state"
    key    = "formal/production/terraform.tfstate"
    region = "us-east-1"
  }
}
Parameterize configurations for reusability:
variable "environment" {
  type = string
}

resource "formal_connector" "main" {
  name = "${var.environment}-connector"
}
Use workspaces or separate state files for prod/staging/dev:
terraform workspace new production
terraform workspace select production
Protect production resources:
resource "formal_resource" "prod_db" {
  name                   = "production-postgres"
  termination_protection = true
  # ...
}

Importing Existing Resources

Bring existing Formal objects under Terraform management:
# Import a Resource
terraform import formal_resource.postgres <resource-id>

# Import a Connector
terraform import formal_connector.main <connector-id>

# Import a User
terraform import formal_user.alice <user-id>
Get object IDs from the Formal console or API.

Outputs

Export useful information:
output "connector_token" {
    description = "Connector API token for deployment"
    value       = formal_connector.production.api_key
    sensitive   = true
  }

  output "connector_listener_port" {
    description = "Configured listener port"
    value       = formal_connector_listener.production_postgres.port
  }

  output "resource_ids" {
    description = "Map of resource names to IDs"
    value = {
      (formal_space.production.name)                       = formal_space.production.id
      (formal_resource.postgres.name)                      = formal_resource.postgres.id
      (formal_connector.production.name)                   = formal_connector.production.id
      (formal_connector_listener.production_postgres.name) = formal_connector_listener.production_postgres.id
      (formal_group.engineering.name)                      = formal_group.engineering.id
    }
  }

Next Steps

Provider Docs

Complete Terraform provider documentation

Examples

Deployment examples on GitHub