Skip to main content
tfts is a drop-in replacement for CDKTF. Migration requires updating imports and regenerating provider bindings.

Migration Steps

Step 1: Update Dependencies

Remove CDKTF packages and add tfts:
# Remove CDKTF and pre-built providers
npm remove cdktf cdktf-cli @cdktf/provider-aws @cdktf/provider-google

# Add tfts
npm install tfts

Step 2: Update Core Imports

Change imports from cdktf to tfts:
// Before
import { App, TerraformStack, TerraformOutput, TerraformVariable, Fn } from "cdktf";

// After
import { App, TerraformStack, TerraformOutput, TerraformVariable, Fn } from "tfts";

Step 3: Update Provider Imports

CDKTF uses pre-built provider packages (@cdktf/provider-*). tfts generates providers locally in .gen/.
// Before (CDKTF pre-built providers)
import { GoogleProvider } from "@cdktf/provider-google/lib/provider";
import { ComputeInstance } from "@cdktf/provider-google/lib/compute-instance";
import * as google from "@cdktf/provider-google";

// After (tfts generated providers - paths relative to your file)
import { GoogleProvider } from "../../../.gen/providers/hashicorp/google/lib/provider/index.js";
import { ComputeInstance } from "../../../.gen/providers/hashicorp/google/lib/compute-instance/index.js";
import * as google from "../../../.gen/providers/hashicorp/google/index.js";
Note: The relative path depth (../../../) depends on your file’s location relative to the project root.

Step 4: Regenerate Provider Bindings

npx tfts get
This reads cdktf.json (or tfts.json) and generates TypeScript classes in .gen/.

Step 5: Update Configuration (Optional)

Rename cdktf.json to tfts.json if desired (both are supported):
mv cdktf.json tfts.json

Automated Migration

Save this script as migrate-from-cdktf.ts and run with bun migrate-from-cdktf.ts .:
import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
import { dirname, join, relative, resolve } from "node:path";

function migrateFile(filePath: string, projectRoot: string): { modified: boolean; changes: string[] } {
  const content = readFileSync(filePath, "utf-8");
  let newContent = content;
  const changes: string[] = [];
  const fileDir = dirname(filePath);
  const relToGen = relative(fileDir, join(projectRoot, ".gen"));

  const cdktfCorePattern = /from\s+["']cdktf["']/g;
  if (cdktfCorePattern.test(newContent)) {
    newContent = newContent.replace(cdktfCorePattern, 'from "tfts"');
    changes.push('from "cdktf" -> from "tfts"');
  }

  const constructsPattern = /from\s+["']constructs["']/g;
  if (constructsPattern.test(newContent)) {
    newContent = newContent.replace(constructsPattern, 'from "tfts"');
    changes.push('from "constructs" -> from "tfts"');
  }

  const namespaceImportPattern = /from\s+["']@cdktf\/provider-([^/"']+)["']/g;
  newContent = newContent.replace(namespaceImportPattern, (_, provider) => {
    const newPath = `${relToGen}/providers/hashicorp/${provider}/index.js`;
    changes.push(`@cdktf/provider-${provider} -> ${newPath}`);
    return `from "${newPath}"`;
  });

  const namedImportPattern = /from\s+["']@cdktf\/provider-([^/"']+)\/lib\/([^"']+)["']/g;
  newContent = newContent.replace(namedImportPattern, (_, provider, modulePath) => {
    const newPath = `${relToGen}/providers/hashicorp/${provider}/lib/${modulePath}/index.js`;
    changes.push(`@cdktf/provider-${provider}/lib/${modulePath} -> ${newPath}`);
    return `from "${newPath}"`;
  });

  if (content !== newContent) writeFileSync(filePath, newContent);
  return { modified: content !== newContent, changes };
}

function findTypeScriptFiles(dir: string): string[] {
  const files: string[] = [];
  const skipDirs = new Set(["node_modules", ".gen", "cdktf.out", "dist", ".git"]);
  function walk(currentDir: string) {
    for (const entry of readdirSync(currentDir, { withFileTypes: true })) {
      const fullPath = join(currentDir, entry.name);
      if (entry.isDirectory() && !skipDirs.has(entry.name)) walk(fullPath);
      else if (entry.isFile() && /\.(ts|tsx)$/.test(entry.name)) files.push(fullPath);
    }
  }
  walk(dir);
  return files;
}

const targetDir = resolve(process.argv[2] || ".");
if (!existsSync(targetDir) || !statSync(targetDir).isDirectory()) {
  console.error(`Error: "${targetDir}" is not a valid directory`);
  process.exit(1);
}

console.log(`Migrating CDKTF -> tfts in: ${targetDir}\n`);
const files = findTypeScriptFiles(targetDir);
let totalModified = 0;

for (const file of files) {
  const { modified, changes } = migrateFile(file, targetDir);
  if (modified) {
    totalModified++;
    console.log(file);
    for (const change of changes) console.log(`  ${change}`);
  }
}

console.log(`\n${totalModified} file(s) modified`);
if (totalModified > 0) console.log("\nNext: npx tfts get");
The script converts:
  • from "cdktf"from "tfts"
  • from "constructs"from "tfts"
  • @cdktf/provider-X{relPath}/.gen/providers/hashicorp/X/index.js
  • @cdktf/provider-X/lib/Y{relPath}/.gen/providers/hashicorp/X/lib/Y/index.js

Import Path Reference

CDKTF Importtfts Import
from "cdktf"from "tfts"
from "constructs"from "tfts"
from "@cdktf/provider-google"from "{relPath}/.gen/providers/hashicorp/google/index.js"
from "@cdktf/provider-google/lib/provider"from "{relPath}/.gen/providers/hashicorp/google/lib/provider/index.js"
from "@cdktf/provider-google/lib/compute-instance"from "{relPath}/.gen/providers/hashicorp/google/lib/compute-instance/index.js"
from "@cdktf/provider-aws"from "{relPath}/.gen/providers/hashicorp/aws/index.js"
from "@cdktf/provider-aws/lib/s3-bucket"from "{relPath}/.gen/providers/hashicorp/aws/lib/s3-bucket/index.js"
{relPath} = relative path from your file to project root (e.g., ../../.. for src/stacks/my-stack/)

API Compatibility

tfts maintains API compatibility with CDKTF core constructs:
ClassStatus
AppCompatible
TerraformStackCompatible
TerraformResourceCompatible
TerraformDataSourceCompatible
TerraformOutputCompatible
TerraformVariableCompatible
TerraformLocalCompatible
TerraformProviderCompatible
FnCompatible
TerraformIteratorCompatible
Backends (S3, GCS, etc.)Compatible

Verification

After migration, verify the generated Terraform JSON is correct:
# Synthesize
npx tfts synth

# Compare output (if you have previous output)
diff -r old-cdktf.out/stacks/my-stack cdktf.out/stacks/my-stack

# Or run terraform plan to check
cd cdktf.out/stacks/my-stack
terraform init
terraform plan

Troubleshooting

Import Errors

If you see import errors after migration, ensure:
  1. You ran npx tfts get to generate providers
  2. Provider imports use .js extension (ESM requirement)
  3. Provider paths match the structure in .gen/

Missing Providers

Add any missing providers to your config:
{
  "terraformProviders": [
    "hashicorp/google@~>6.0",
    "hashicorp/aws@~>5.0"
  ]
}
Then regenerate: npx tfts get