Map API Entities to Terraform Resources

Add the x-speakeasy-entity annotation to objects in your OpenAPI spec to include them as entities in the Terraform provider.

As a component:

components:
schemas:
Order:
description: An order helps you make coffee
x-speakeasy-entity: Order
properties:
id:
type: integer
description: Numeric identifier of the order.
name:
type: string
description: Product name of the coffee.
price:
type: number
description: Suggested cost of the coffee.
required:
- name
- price
type: object

Or inline in a path:

paths:
/order:
post:
tags:
- Order
summary: Create a coffee order
x-speakeasy-entity-operation: Order#create
requestBody:
content:
application/json:
schema:
x-speakeasy-entity: Order
properties:
id:
type: integer
description: Numeric identifier of the order.
name:
type: string
description: Product name of the coffee.
price:
type: number
description: Suggested cost of the coffee.
required:
- name
- price
type: object
resource "yourprovider_order" "example" {
name = "Filter Blend"
price = 11.5
}

Where you place the x-speakeasy-entity annotation affects the Terraform provider structure.

  • At the top level: Properties are nested objects.
  • At a lower level: Properties above the annotation are flattened.

Top Level

Pet:
x-speakeasy-entity: Pet
...
resource "yourprovider_pet" "example" {
data = { name = "Filter Blend" }
}

Lower Level

Pet:
properties:
data:
x-speakeasy-entity: Pet
...
resource "yourprovider_pet" "example" {
name = "Filter Blend"
}
Warning Icon

Warning

Properties above the x-speakeasy-entity annotation are flattened, which could cause conflicts. Apply the annotation carefully to align the structure of the Terraform provider with your API’s intended user interaction.

Specify CRUD Operations for API Endpoints

The x-speakeasy-entity-operation annotation specifies CRUD (create, read, update, and delete) operations associated with each endpoint in your OpenAPI spec for a Terraform entity. The value determines the behavior of operations such as create, read, update, and delete and is structured as Entity#operation,operation,...#order:

  • Entity represents the name of the entity.
  • operation can be one or more of create, read, update, and delete, concatenated with commas.
  • order is optional and can be used to define additional API calls that should be invoked for a given CRUD invocation.

Behavior of Operations

  • Entity:create makes the entity a Terraform resource.
  • Entity:read ensures consistency with Terraform state, updates attributes, and generates a data source.
  • Entity:update provides update support for the resource. Without it, any attribute change requires resource replacement (ForceNew).
  • Entity:delete enables deletion of the resource. Without it, no action is taken on deletion.
  • Entity:create,update (idempotent operations) indicates the API is idempotent. Combine these operations to allow the same API call to create new objects and update existing ones, depending on attribute changes.
paths:
/pet:
post:
tags:
- pet
summary: Add a new pet to the store
x-speakeasy-entity-operation: Pet#create
/pet/{petId}:
get:
tags:
- pet
summary: Info for a specific pet
x-speakeasy-entity-operation: Pet#read
update:
tags:
- pet
summary: Update the pet
x-speakeasy-entity-operation: Pet#update
delete:
tags:
- pet
summary: Delete the pet
x-speakeasy-entity-operation: Pet#delete

Multiple API Operations for One Resource

When multiple API operations are necessary for a single resource, use the additional entity-ordering capabilities of the x-speakeasy-entity-operation annotation.

paths:
/pet/{petId}:
get:
x-speakeasy-entity-operation: Pet#read#1
/animal:
get:
x-speakeasy-entity-operation: Pet#read#2

Multiple API operations for one resource can be combined with multiple entity operations of one API operation for multiple resources as necessary.

One API Operation for Multiple Resources

When a single API operation is necessary for multiple resources, use multiple entity operation entries with the x-speakeasy-entity-operation annotation.

parameters:
- in: query
name: id
required: false
schema:
type: string
operationId: GetAnimal
x-speakeasy-entity-operation:
- Cat#read
- Dog#read

One API operation for multiple resources can be combined with the entity operation ordering of multiple API operations for one resource as necessary.

Remap API Property to Terraform Attribute Name

The x-speakeasy-name-override annotation adjusts the Terraform attribute name within a resource while remapping all the API data handling internally. This is useful, for example, to standardize differing API property names across operations to a single attribute name.

unique_id:
type: string
x-speakeasy-name-override: id

The annotation also has other SDK customization capabilities, however, those are generally unnecessary for Terraform providers as the generated Go SDK is internal to the provider code.

Align API Parameter With Terraform Property

The x-speakeasy-match annotation adjusts the API parameter name to align with a Terraform state property. If mismatches occur, a generation error will highlight appropriate root-level properties for accurate mapping.

paths:
/pet/{petId}:
delete:
parameters:
- name: petId
x-speakeasy-match: id
x-speakeasy-entity-operation: Pet#delete

Hide Sensitive Properties

Properties marked as x-speakeasy-param-sensitive will be concealed from the console output of Terraform. This helps to ensure the confidentiality of sensitive data within Terraform operations.

components:
schemas:
Pet:
type: object
properties:
name:
type: string
secret:
type: string
x-speakeasy-param-sensitive: true

Exclude Property From Terraform State

When x-speakeasy-terraform-ignore: true, this extension ensures the specified property and any interactions involving it are omitted from Terraform’s state management.

Info Icon

Info

This extension completely suppresses the property from the Terraform state. If you want to suppress a specific operation, use x-speakeasy-ignore: true to omit the operation from the annotated CRUD method. For example, if a field is present in both the CREATE and READ response bodies, omitting it from the READ response body will turn off drift detection for that field. The field will remain in the CREATE response body and the Terraform state.

components:
schemas:
Pet:
x-speakeasy-entity: Pet
type: object
properties:
optionalMetadata:
x-speakeasy-terraform-ignore: true
type: string
name:
type: string
required:
- name
resource "petstore_pet" "mypet" {
name = "myPet"
# Attempting to set an ignored parameter results in an error
# optionalMetadata = true
}

Allow JSON String Attributes

Set the x-speakeasy-type-override extension to any to convert the associated attribute to a JSON string. This allows for inline the specification of the attribute’s value, accommodating attributes with variable or dynamic structures.

components:
schemas:
Pet:
x-speakeasy-entity: Pet
type: object
properties:
deep:
x-speakeasy-type-override: any
type: object
properties:
object:
type: object
additionalProperties: true
properties:
in:
type: object
properties:
here:
type: string
name:
type: string
required:
- name
resource "petstore_pet" "mypet" {
name = "myPet"
deep = jsonencode({
object = {
with = "anything"
defined = true
}
})
}

Suppress Unnecessary Plan Changes

Setting the x-speakeasy-param-suppress-computed-diff to true suppresses unnecessary Terraform plan changes for computed attributes that are not definitively known until after application. This is useful in scenarios where computed attributes frequently cause spurious plan changes.

components:
schemas:
Pet:
x-speakeasy-entity: Pet
type: object
properties:
name:
type: string
status:
x-speakeasy-param-suppress-computed-diff: true
type: string
Warning Icon

Warning

Applying this modifier when x-speakeasy-entity-operation: my_resource#read is not defined may result in drift between the Terraform plan and remote state should updates to attributes happen outside of Terraform changes. Please only apply this when necessary.

Prevent Conflicting Attributes

The x-speakeasy-conflicts-with extension indicates that a property conflicts with another, ensuring that certain combinations of properties are not set together. This is ideal for situations where certain attributes are mutually exclusive or setting one attribute invalidates another.

components:
schemas:
Pet:
x-speakeasy-entity: Pet
type: object
properties:
name:
type: string
name_prefix:
type: string
x-speakeasy-conflicts-with: name
id:
type: string
generated_name_options:
type: object
properties:
prefix:
type: string
x-speakeasy-conflicts-with:
- ../name_prefix
- ../name
- ../id
resource "example_pet" "happy_pet" {
name = "Mrs Poppy"
name_prefix = "Mrs"
}
$ terraform plan
│ Error: Invalid Attribute Combination
│ with example_pet.happy_pet,
│ on provider.tf line 39, in resource "example_pet" "happy_pet":
│ 3: name_prefix = "test"
│ Attribute "name" cannot be specified when "name_prefix" is specified

Configuration Validation

Speakeasy automatically generates certain Terraform configuration value validation handlers based on your OpenAPI specification. When configuration validation is defined, Terraform raises invalid value errors before users can apply their configuration for a better user experience.

By default, these OpenAPI specification properties are automatically handled:

  • For string types: enum, maxLength, minLength, and pattern
  • For integer types: enum, minimum, and maximum
  • For array types: maxItems, minItems, and uniqueItems

For use cases not automatically handled, add custom validation logic or reach out to the team.

Add Custom Validation Logic

Use the x-speakeasy-plan-validators extension to add custom validation logic to Terraform plan operations and ensure configurations meet predefined criteria before execution. This extension is essential for scenarios requiring advanced validation logic that JSON Schema cannot accommodate.

components:
schemas:
Pet:
type: object
x-speakeasy-entity: Pet
properties:
name:
type: string
age:
type: integer
x-speakeasy-plan-validators: AgeValidator

In this scenario, when Speakeasy next generates the Terraform provider, it will bootstrap a custom validator file located at internal/validators/int64validators/age_validator.go, and import the schema configuration wherever x-speakeasy-plan-validators: AgeValidator is referenced. You can modify the validator file to contain your logic.

Implementation Notes

  1. A plan validator is a type conformant to the terraform-plugin-framework expected interface. A unique plan validator will be bootstrapped in the appropriate subfolder for the Terraform type it is applied to: boolvalidators, float64validators, int64validators, listvalidators, mapvalidators, numbervalidators, objectvalidators, setvalidators, or stringvalidators. Speakeasy will always create and use a file as snake_case.go for a given x-speakeasy-plan-validators value.

  2. A plan validator operates on the raw (untyped) Terraform value types. However, you can convert a Terraform type to a value type Speakeasy manages (type_mytype.go) by using the included reflection utility. This is useful for applying validators to complex types like list, map, object, and set.

  3. While working with a plan validator, you have the ability to perform various tasks, including initiating network requests. However, it’s important to ensure that plan validations do not result in any unintended side effects. Please refer to the HashiCorp guidance on plan validator development (opens in a new tab) or reach out in our Slack if you have questions.

  4. It is possible to have an array of plan validators, for example, x-speakeasy-plan-validators: [MinAgeValidator, MaxAgeValidator].

  5. A validator can only be applied to a resource attribute. Validators cannot be applied at the same level as the x-speakeasy-entity annotation because that becomes the “root” of the Terraform resource. However, validators can access or refer to any data in the entire resource (for an example, see the x-speakeasy-conflicts-with validator). The annotation will be ignored for data sources.

  6. Speakeasy regenerations do not delete user-written code. If the validator is no longer in use, it will be ignored (no longer referenced) but the source file will remain. You might want to delete such an orphaned validation file for repository hygiene.

Add Custom Plan Modification Logic

Use the x-speakeasy-plan-modifiers extension to add custom plan modification logic to Terraform plan operations. Plan modifiers enable advanced default value, resource replacement, and difference suppression logic.

components:
schemas:
Pet:
type: object
x-speakeasy-entity: Pet
properties:
name:
type: string
age:
type: integer
x-speakeasy-plan-modifiers: AgeModifier

In this scenario, when Speakeasy next generates the Terraform provider, it will bootstrap a custom plan modifier file located at internal/planmodifiers/int64planmodifier/age_modifier.go, and import the schema configuration wherever x-speakeasy-plan-modifiers: AgeModifier is referenced. You can edit the plan modifier file to contain your logic.

Implementation Notes

  1. A plan modifier is a type conformant to the terraform-plugin-framework expected interface. A unique plan modifier will be bootstrapped in the appropriate subfolder for the Terraform type it is applied to: boolplanmodifiers, float64planmodifiers, int64planmodifiers, listplanmodifiers, mapplanmodifiers, numberplanmodifiers, objectplanmodifiers, setplanmodifiers, or stringplanmodifiers. Speakeasy will always create and use a file as snake_case.go for a given x-speakeasy-plan-modifiers value.

  2. A plan modifier operates on the raw (untyped) Terraform value types. However, you can convert a Terraform type to a value type Speakeasy manages (type_mytype.go) by using the included reflection utility. This is useful for applying modifiers to complex types like list, map, object, and set.

  3. While working with a plan modifier, you have the ability to perform various tasks, including initiating network requests. However, it’s important to ensure that plan modifiers do not result in any unintended side effects. Please refer to the HashiCorp guidance on plan modifier development (opens in a new tab) or reach out in our Slack if you have questions.

  4. It is possible to have an array of plan modifiers, for example, x-speakeasy-plan-modifiers: [FirstModifier, SecondModifier].

  5. A modifier can only be applied to a resource attribute. The annotation will be ignored for data sources. Modifiers cannot be applied at the same level as the x-speakeasy-entity annotation because that becomes the “root” of the Terraform resource.

  6. Speakeasy regenerations do not delete user-written code. If the modifier is no longer in use, it will be ignored (no longer referenced) but the source file will remain. You might want to delete such an orphaned modifier file for repository hygiene.

Specify Resource Version

The x-speakeasy-entity-version extension specifies the version of a given resource and should only be used if you need to write a state migrator, for instance, if you are changing the type of a field.

Terraform resource versions are zero-indexed and default to 0. For your first breaking change requiring a state migrator, set x-speakeasy-entity-version: 1. Each state migrator function must migrate from the previous version of the state.

If this is set, a boilerplate state upgrader will be written and hooked into internal/stateupgraders/your_resource_v1.go. Please refer to the Terraform documentation (opens in a new tab) for guidance on writing a state migrator.

Configuring Environment Values

Use the environmentVariables configuration in your gen.yaml to set up default values for provider variables to be pulled in from a user environment variable. This is useful for mapping known environment values that will hold an API key into the provider.

terraform:
environmentVariables:
- env: EXAMPLE_SERVER_URL_FROM_ENV_VAR
providerAttribute: server_url
- env: EXAMPLE_ACCESS_TOKEN
providerAttribute: access_token

The environmentVariables configuration is expected to be a list of objects with {env: string, providerAttribute: string} keys and values. These will create associations from environment variables (referenced as env) with provider attributes (referenced as providerAttribute).

Custom Resources or Data Sources

If you would like to include an existing resource that is outside of the Speakeasy-generated provider, reference it in gen.yaml like so:

terraform:
additionalResources:
- importAlias: custom
importLocation: github.com/custom/terraform-provider-example/src/custom_resource
resource: custom.NewCustomResource
additionalDataSources:
- importAlias: custom
importLocation: github.com/custom/terraform-provider-example/src/custom_datasource
datasource: custom.NewCustomDataSource

The additionalResources key is expected to contain a list of { importLocation?: string, importAlias?: string, resource: string } objects. Each resource will be inserted into the provider resource list. If importLocation or importAlias is defined, Speakeasy will add that to the import list at the top of the provider file. The value of resource is arbitrary text, and could contain a function invocation if desired.

The additionalDataSources key follows the same syntax, but with datasource as the text string to be inserted into the list instead of resource.

To learn more about how to write a Terraform resource, please consult the official Terraform documentation (opens in a new tab).

Modifying Resource and Data Source Descriptions

The x-speakeasy-entity-description extension allows you to modify the description of a Terraform resource or data source. This is useful when augmenting the documentation in your OpenAPI specification with documentation for your specific resources. This documentation is expected to be in Markdown format. Use this extension alongside your x-speakeasy-entity extension.

Alternatively, a template folder can be written to customize any or all aspects of generated documentation in alignment with terraform-plugin-docs (opens in a new tab).

components:
schemas:
Order:
description: An order helps you make coffee
x-speakeasy-entity: Order
x-speakeasy-entity-description: |
The order resource allows you to declaratively construct an order for coffee.
resource "speakeasy_order" "example" {
name = "Filter Blend"
price = 11.5
}

Deduplicate Terraform Types

The terraform types folder includes a representation of your data models that is appropriate for the terraform-plugin-framework type system. However, if you have multiple types with the same signature (e.g. the same set of child property types), there might be a lot of these types that are effectively duplicated. To minimize the git repository / binary size, it might make sense to deduplicate these by re-using types with the same signature across different resources. If you would like to enable this, set the following configuration option:

This option is false by default.

terraform:
enableTypeDeduplication: true

Speciality Annotations

The annotations in this section are not commonly used within Speakeasy. We recommend contacting our team to help determine if they are correct for you.

Force Mark Property as Read-Only

The x-speakeasy-param-readonly extension marks a property as read-only. Any user attempt to modify it in Terraform will result in a runtime error. This prevents unintended changes to critical properties in Terraform configurations.

components:
schemas:
Pet:
type: object
properties:
name:
type: string
id:
type: integer
x-speakeasy-param-readonly: true

Force Designate a Property as Optional

Apply x-speakeasy-param-optional to any property to designate it as optional. This extension takes precedence over the required attribute in the JSON Schema specification, providing flexibility in Terraform configurations by allowing optional settings for certain properties.

components:
schemas:
Pet:
type: object
properties:
name:
type: string
id:
type: integer
x-speakeasy-param-optional: true

Force Resource Recreation on Property Change

Properties marked with x-speakeasy-param-force-new will cause the associated Terraform resource to be destroyed and recreated whenever the property value changes. This ensures that any alteration to the property triggers a complete recreation of the object.

components:
schemas:
Pet:
type: object
properties:
name:
type: string
id:
type: integer
x-speakeasy-param-force-new: true

Update Behavior for Plan-Only Attributes

The x-speakeasy-terraform-plan-only extension ensures that only the values from the Terraform plan are used during updates, overriding any prior state or default values provided by the API. By preventing prior state values from being merged into the update request, the annotation ensures that omitted or null values in the plan are correctly reflected in API calls.

components:
schemas:
Pet:
type: object
properties:
properties:
name:
type: string
id:
type: integer
nullable: true
x-speakeasy-terraform-plan-only: true

Keywords

Success Icon

Tip

This section is not an exhaustive list of available keyword options. If you’re unsure whether a keyword is supported, please reach out to our team at support@speakeasyapi.dev.

The anyOf Keyword

Terraform has limited support for the anyOf keyword due to its less flexible type system than JSON Schema. For instance, managing anyOf with multiple subtypes requires a large set of combined types, leading to practical and implementation challenges.

Consider replacing anyOf in your schema with oneOf or allOf. This adjustment aligns with Terraform’s capabilities: oneOf for union types and allOf for intersection types.

For more guidance or to discuss schema adaptations, contact our support team at support@speakeasyapi.dev.

The oneOf Keyword

In Terraform, oneOf is defined as a SingleNestedAttribute where each potential child is represented by a unique key. To ensure compliance with oneOf semantics, conflicts-with plan validators are added to confirm that only one of these keys is active at any given time.

If a oneOf is declared at the root level of an entity, the Speakeasy generator will extract common property attributes and duplicate them into the root level. This is important if, for instance, a common id property is required for making read, update, or delete requests.

The allOf Keyword

For allOf, Speakeasy merges all sub-schemas into a single combined attribute, creating a unified schema component that encapsulates all specified properties.