Type-safe IaC for Dart.
Cloud infrastructure as real Dart code — typed, refactor-safe, drop-in for
terraform apply.
Alpha — no SemVer until v1.0.0, but breaking changes land only on minor bumps. Pin ^0.26.x, read MIGRATING.md before minor bumps, and see status on terradart.dev.
See terradart.dev for documentation, guides, and API reference.
| Package | Description | Pub |
|---|---|---|
terradart_core |
Core runtime — Stack, Resource, Provider, Data, TfArg, and synth behavior. |
|
terradart_google |
Curated factory wrappers for Google Cloud resources (hashicorp/google). |
|
terradart_google_beta |
Curated factory wrappers for beta-only Google Cloud resources (hashicorp/google-beta). |
|
terradart_appwrite |
Curated factory wrappers for Appwrite resources (appwrite/appwrite). |
|
terradart_cloudflare |
Curated factory wrappers for Cloudflare resources (cloudflare/cloudflare). |
|
terradart_agent |
MCP server (terradart-mcp) exposing the curated factory catalog to AI agents. |
(unlisted) |
terradart_codegen |
Maintainer generation tooling and CLI (terradart wrap). |
# pubspec.yaml
dependencies:
terradart_core: ^0.26.x
terradart_google: ^0.26.x// docs:pitch:start
// infra/lib/app_infra.dart
// A Stack is one Terraform root module of GCP resources, written in Dart.
import 'package:terradart_core/terradart_core.dart';
import 'package:terradart_google/cloud_run.dart';
import 'package:terradart_google/cloud_sql.dart';
import 'package:terradart_google/iam.dart';
import 'package:terradart_google/provider.dart';
final class AppInfraStack extends Stack {
AppInfraStack({required String projectId})
: super(providers: [
GoogleProvider(project: projectId, region: 'asia-northeast1'),
]) {
add(GoogleSqlDatabaseInstance(
localName: 'app_sql',
name: TfArg.literal('app-sql'),
databaseVersion: TfArg.literal(DatabaseVersion.postgres15),
region: TfArg.literal('asia-northeast1'),
settings: SqlDatabaseInstanceSettings(
tier: TfArg.literal('db-f1-micro'),
),
));
final runSa = add(GoogleServiceAccount(
localName: 'run_sa',
accountId: TfArg.literal('app-run-sa'),
));
add(GoogleProjectIamMember(
localName: 'run_sa_sql_client',
project: TfArg.literal(projectId),
role: TfArg.literal('roles/cloudsql.client'),
member: TfArg.ref(runSa.iamMember),
));
add(GoogleCloudRunV2Service(
localName: 'app',
name: TfArg.literal('app'),
location: TfArg.literal('asia-northeast1'),
template: CloudRunV2ServiceTemplate(
serviceAccount: TfArg.ref(runSa.email),
containers: [
CloudRunV2ServiceServiceContainer(
name: TfArg.literal('app'),
image: TfArg.literal('gcr.io/cloudrun/hello'),
ports: CloudRunV2ServiceContainerPort(
containerPort: TfArg.literal(8080),
),
env: [
CloudRunV2ServiceEnvVar(
name: TfArg.literal('DATABASE_URL'),
source: CloudRunV2ServiceEnvVarFromLiteral(
TfArg.literal(
'postgresql://app-client@${projectId}.iam@localhost:5432/app',
),
),
),
],
),
CloudRunV2ServiceServiceContainer(
name: TfArg.literal('cloud-sql-proxy'),
image: TfArg.literal(
'gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.18.1',
),
args: TfArg.literal([
'--port=5432',
'--auto-iam-authn',
'${projectId}:asia-northeast1:app-sql',
]),
),
],
),
));
}
}
// docs:pitch:enddart pub get
dart run bin/infra.dart # synth → tf-out/
cd tf-out && terraform init && terraform applyPer-service imports (cloud_run.dart, cloud_sql.dart, …) keep IDE completion scoped; the legacy package:terradart_google/terradart_google.dart barrel re-export remains supported.
Runnable end-to-end example: examples/pubsub_quickstart/.
the boundary = the place where infrastructure values (topic IDs, queue names, secret refs, IAM members) flow into runtime Dart code. Today that boundary is held together by string literals on both sides:
- A Pub/Sub topic name is hand-typed in HCL and again as a string literal in a Cloud Function.
- A renamed secret silently breaks runtime resolution because the reference is a string.
- IAM binding members drift between modules with no compiler visibility.
TerraDart makes this boundary a first-class artifact. The same Topic object whose ID is consumed by terraform apply is exported as a typed Dart constant your Firebase Function imports — and dart analyze catches drift the moment it happens.
// infra/lib/orders_stack.dart
import 'package:terradart_core/terradart_core.dart';
import 'package:terradart_google/provider.dart';
import 'package:terradart_google/pubsub.dart';
final class OrdersStack extends Stack {
OrdersStack({required String projectId})
: super(providers: [GoogleProvider(project: projectId)]) {
final orders = GooglePubsubTopic(
localName: 'orders',
name: TfArg.literal('orders-prod'),
messageRetentionDuration: TfArg.literal('604800s'),
);
add(orders);
addExport('ORDERS_TOPIC', ResourceIdExport(orders.nameRef));
}
}// functions/lib/orders_handler.dart (regenerated on synth)
import 'package:my_app_infra/exports.g.dart';
Future<void> handle(PubsubEvent event) async {
if (event.topic == OrdersStackExports.ORDERS_TOPIC) {
// ... process event
}
}Rename orders-prod in the Stack and the handler will not compile until the reference is fixed.
GoogleStorageBucket(
localName: 'assets',
name: TfArg.literal('my-app-assets-prod'),
storageClass: TfArg.literal(BucketStorageClass.standard), // not 'STANDARD'
);
// BucketStorageClass.standerd ← typo: compile errorThe .terraformValue getter convention encodes BucketStorageClass.standard as "STANDARD" at synth time. ArgumentError (not silent wrong JSON) on missing convention.
GoogleCloudRunV2Service(
template: Template(
containers: [ServiceContainer(
image: TfArg.literal('gcr.io/cloudrun/hello'),
env: [
EnvVar(name: 'LOG_LEVEL',
source: EnvVarFromLiteral(TfArg.literal('info'))),
EnvVar(name: 'DB_PASSWORD',
source: EnvVarFromSecret(secret: TfArg.literal('db-pwd'))),
],
)],
),
);EnvVarSource is a sealed type — the compiler keeps env.value and env.value_source.secret_key_ref mutually exclusive. Same pattern for BigQuery's 8-variant Access, Cloud Storage's BucketObjectContent, Cloud Run's VolumeSource.
Stack subclasses are regular Dart classes. Loops, conditionals, env config, dependency injection — all work the way they already work. There is no synth CLI; you call stack.writeTo('tf-out') from your own bin/infra.dart (or stack.synth() for an in-memory SynthResult without writing to disk).
Alpha. terradart-mcp is an MCP server that exposes the curated factory catalog to coding agents (Claude Code, Cursor, Claude Desktop). Five read-only tools — list_barrels, list_resources, get_resource_schema, get_quickstart, and check_coverage — help agents author correct Dart without guessing factory names. It does not run Terraform or touch GCP.
brew install nozomi-koborinai/tap/terradart-mcp
# or download from GitHub releases — see packages/terradart_agent/README.mdDocs: terradart.dev/docs/agent/
terradart_googleships 1333 curated resource factories + 461 data sources (1794 catalog entries) across per-service barrels (compute,pubsub,cloud_run,bigquery, …). The GAhashicorp/googlecatalog is filled.terradart_google_betaships the beta-onlyhashicorp/google-betacatalog (128 resource factories, schema pin tracking the weekly GA bump).terradart_appwriteships the filledappwrite/appwritecatalog at2.0.0-beta.1(38 resource factories + 24 data sources).terradart_cloudflareships the filledcloudflare/cloudflarecatalog at5.23.0(257 resource factories + 446 data sources). Nested plugin-framework objects are typed Dart helpers.
Explore ready-to-run examples in examples/:
- Foundational & IAM: Pub/Sub, Cloud Tasks, Secret Manager, IAM
- Compute & Networking: Compute & Firewall, GKE, Cloud DNS
- Data & Storage: Cloud Storage, BigQuery, Cloud Bigtable, KMS
- Application Platform: Cloud Run v2, Cloud Monitoring, Workflows, Eventarc
- AI & Agents: Vertex AI, Agentic Applications
See the full factory table on terradart.dev/docs/coverage/.
| TerraDart | HCL | CDKTF | Pulumi | |
|---|---|---|---|---|
| Dart authoring | ✅ | ❌ | ❌ (TS / Py / Java / Go) | ❌ (TS / Py / Go / etc.) |
| Type-safe handoff to your app | ✅ (compile-time) | ❌ (terraform output + parse) |
❌ (no Dart) | ❌ (no Dart) |
Drop-in for terraform apply |
✅ (emits *.tf.json) |
✅ (native) | ✅ | |
| Project status | Alpha | Mature | Archived Dec 2025 | Active |
- Not a Terraform replacement. TerraDart synthesizes JSON;
terraform plan / applyruns as before. State stays where you already keep it. - Not a multi-cloud abstraction layer. Curated wrappers faithfully mirror provider schemas rather than imposing cross-cloud abstractions.
- Not a constructs framework. Composite abstractions are out of scope for the pre-1.0 cycle.
- Not module-block support. Compose Terraform modules in HCL alongside TerraDart-generated
*.tf.json— both feed the sameterraform apply.
Alpha, pre-1.0 (0.26.x). No SemVer until v1.0.0, but breaking changes land only on minor bumps, always documented in MIGRATING.md; pin ^0.26.x and take patches freely. Beta needs external validation — see the path to beta. Expectations: terradart.dev/docs/status/.
See CONTRIBUTING.md. For security issues, use the GitHub private security advisory flow.
"Terraform" is a registered trademark of HashiCorp, Inc.
Dart™ and the related logo are trademarks of Google LLC. We are not endorsed by or affiliated with Google LLC.
TerraDart is an independent open-source project and is not affiliated with, endorsed by, or sponsored by HashiCorp or Google.
Apache-2.0. See LICENSE.
The framing draws on prior work in CDKTF (archived Dec 2025), AWS CDK, and Pulumi.