DynamoDB Terraform, CDK and CloudFormation Generator
Paste the output of aws dynamodb describe-table and get the table back as Terraform, AWS CDK or CloudFormation, global-table replicas included. It runs in your browser; nothing you paste is sent anywhere.
dynamodb-table-iac is the open-source (MIT) library behind this tool.
Bring a Console-built table under code
Most tables start in the Console or a one-off script. Writing the matching resource by hand means copying every key, index and capacity setting out of describe-table and hoping you missed nothing. This tool reads that same output and writes the definition for you.
Every generated file lists what it leaves out and what applying it over the live table would change, so you read the trade-offs before terraform plan does. The Terraform file also carries a commented import block for adopting the existing table.
The targets differ. The Terraform AWS provider has no vector-index support, so a vector index becomes a commented definition. CloudFormation always uses AWS::DynamoDB::GlobalTable, with a single-region table as one replica, and matches the CDK output resource for resource.
Worked example: a two-region table
The describe-table output below is for an on-demand table with a sort key, one GSI and a replica in us-west-2. The Terraform under it is exactly what the tool produces for it.
{
"Table": {
"TableName": "orders",
"TableArn": "arn:aws:dynamodb:us-east-1:123456789012:table/orders",
"TableStatus": "ACTIVE",
"KeySchema": [
{ "AttributeName": "PK", "KeyType": "HASH" },
{ "AttributeName": "SK", "KeyType": "RANGE" }
],
"AttributeDefinitions": [
{ "AttributeName": "PK", "AttributeType": "S" },
{ "AttributeName": "SK", "AttributeType": "S" },
{ "AttributeName": "status", "AttributeType": "S" },
{ "AttributeName": "createdAt", "AttributeType": "N" }
],
"BillingModeSummary": { "BillingMode": "PAY_PER_REQUEST" },
"ProvisionedThroughput": {
"NumberOfDecreasesToday": 0,
"ReadCapacityUnits": 0,
"WriteCapacityUnits": 0
},
"GlobalSecondaryIndexes": [
{
"IndexName": "by-status",
"KeySchema": [
{ "AttributeName": "status", "KeyType": "HASH" },
{ "AttributeName": "createdAt", "KeyType": "RANGE" }
],
"Projection": { "ProjectionType": "KEYS_ONLY" },
"IndexStatus": "ACTIVE"
}
],
"StreamSpecification": { "StreamEnabled": true, "StreamViewType": "NEW_AND_OLD_IMAGES" },
"GlobalTableVersion": "2019.11.21",
"Replicas": [{ "RegionName": "us-west-2", "ReplicaStatus": "ACTIVE" }],
"DeletionProtectionEnabled": true
}
}# dynamodb-table-iac: Terraform for DynamoDB table "orders"
# Source: aws dynamodb describe-table, region us-east-1
#
# Not emitted (configure these yourself if the live table uses them):
# - point-in-time recovery (DescribeTable does not return it; a replica declared without it has PITR off)
# - tags (DescribeTable does not return them; applying removes live tags)
# - auto-scaling policies (a fixed capacity snapshot is emitted instead; applying replaces the policy with that snapshot)
# - warm throughput (DescribeTable reports the CURRENT value, which grows with traffic; emitting it would bill a pre-warm)
# - contributor insights, Kinesis streaming destinations and resource policies (DescribeTable does not return them)
# - the KMS key ARN of an SSE-encrypted table (DescribeTable cannot tell an AWS-managed key from a customer-managed one; the AWS-managed key is emitted and the live ARN is left in a comment)
# - settings of non-home replicas that DynamoDB never synchronizes (their deletion protection, PITR and tags are only visible from their own region)
#
# Notes:
# - TTL: not provided — include the output of `aws dynamodb describe-time-to-live` to add it.
#
# To adopt the live table instead of creating a new one, uncomment this import
# block, then run `terraform plan` and check that it reports no changes:
# import {
# to = aws_dynamodb_table.orders
# id = "orders"
# }
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.29"
}
}
}
provider "aws" {
region = "us-east-1"
}
resource "aws_dynamodb_table" "orders" {
name = "orders"
billing_mode = "PAY_PER_REQUEST"
hash_key = "PK"
range_key = "SK"
stream_enabled = true
stream_view_type = "NEW_AND_OLD_IMAGES"
deletion_protection_enabled = true
attribute {
name = "PK"
type = "S"
}
attribute {
name = "SK"
type = "S"
}
attribute {
name = "status"
type = "S"
}
attribute {
name = "createdAt"
type = "N"
}
global_secondary_index {
name = "by-status"
projection_type = "KEYS_ONLY"
key_schema {
attribute_name = "status"
key_type = "HASH"
}
key_schema {
attribute_name = "createdAt"
key_type = "RANGE"
}
}
# Replica blocks default point_in_time_recovery, deletion_protection_enabled and
# propagate_tags to false: applying this file turns them off on the replicas
# below unless you set them here.
replica {
region_name = "us-west-2"
consistency_mode = "EVENTUAL"
}
}
What the generated code includes
The key schema and the attribute definitions those keys reference, billing mode and capacity for the table and each index, global and local secondary indexes, TTL, deletion protection, streams, encryption, table class, on-demand maximum throughput, vector indexes where the target supports them, and global-table replicas with their consistency mode.
What it leaves out
describe-table does not return everything a table carries. The header of every generated file lists these, with the reason:
- point-in-time recovery (DescribeTable does not return it; a replica declared without it has PITR off)
- tags (DescribeTable does not return them; applying removes live tags)
- auto-scaling policies (a fixed capacity snapshot is emitted instead; applying replaces the policy with that snapshot)
- warm throughput (DescribeTable reports the CURRENT value, which grows with traffic; emitting it would bill a pre-warm)
- contributor insights, Kinesis streaming destinations and resource policies (DescribeTable does not return them)
- the KMS key ARN of an SSE-encrypted table (DescribeTable cannot tell an AWS-managed key from a customer-managed one; the AWS-managed key is emitted and the live ARN is left in a comment)
- settings of non-home replicas that DynamoDB never synchronizes (their deletion protection, PITR and tags are only visible from their own region)
Frequently asked questions
Is my table definition sent to a server?
No. The code is generated in your browser from the text you paste. Nothing is uploaded, and the page never calls AWS.
How do I include TTL?
TTL is not part of describe-table output. Run aws dynamodb describe-time-to-live for the table and paste its TimeToLiveDescription object next to Table in the same JSON. Without it, the generated file says TTL was not provided.
Which region does the generated code target?
The table’s home region, read from its TableArn. DescribeTable lists every other region as a replica, so run it in the region you manage the table from. Output captured from DynamoDB Local carries no real region, so set the region yourself.
Can I apply it to the table that already exists?
Yes, after reading the header. The Terraform file includes a commented import block: uncomment it and run terraform plan. The plan should change only what the header lists, such as removing live tags, so check each change it reports against that list.