TerraDart Type-safe IaC for Dart.

Typed outputs, straight into your Flutter app.

terradart apply

Write your cloud as a Dart class — Google Cloud, AWS, Cloudflare, Appwrite. The compiler checks every reference between resources, one command provisions it, and the values your app needs arrive as typed Dart.

dart pub global activate terradart_cli

How it works

From a Dart class to a running app

The Stack declares the infrastructure and the values the app reads. One command provisions it and writes those values for the client build. Rename an output and the app stops compiling — not in production.

  1. 1Write a Stack

    lib/app_stack.dart
    final class AppStack extends Stack {
      AppStack({required String projectId})
        : super(
            providers: [GoogleProvider(project: projectId)],
            appExports: AppExports('lib/generated/app_stack.app.dart'),
          ) {
        final api = add(GoogleCloudRunV2Service(
          'api',
          name: .literal('app-api'),
          location: .literal('asia-northeast1'),
          template: CloudRunV2ServiceTemplate(
            containers: [
              .new(image: .literal('us-docker.pkg.dev/my-app/web/api')),
            ],
          ),
        ));
    
        addOutput('api_url', api.uri);
        addDartDefineOutput();
      }
    }
  2. 2Apply it

    terradart apply

    • synth runs bin/infra.dart
    • plan + apply provisions the Stack
    • outputs .terradart/dart_defines.json

    Uses the engine on your PATH, or downloads a pinned, checksum-verified one.

  3. 3Read it in Flutter

    lib/api_client.dart
    import 'generated/app_stack.app.dart';
    
    const outputs = AppStackOutputs.fromDartDefine();
    
    Uri endpoint(String path) =>
        Uri.parse(outputs.apiUrl).resolve(path);

    flutter run \
      --dart-define-from-file=.terradart/dart_defines.json

Outputs in client apps The terradart command How TerraDart works

Providers

One API, whichever cloud you run on

Every package wraps its provider's full catalog with the same rules: an argument that names another resource takes its ref, fixed values are enums, and exactly-one-of choices are sealed types. One Stack can mix them.

examples/pubsub_quickstart/lib/main.dart Full example
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, region: 'us-central1')],
        appExports: AppExports('lib/generated/orders_stack.app.dart'),
      ) {
    // …
    final topic = add(
      GooglePubsubTopic(
        'orders',
        name: .literal('orders-prod'),
        messageRetentionDuration: .literal(
          const Duration(days: 7).toTfDurationString(),
        ),
      ),
    );
    add(
      GooglePubsubSubscription(
        'orders_push',
        name: .literal('orders-push'),
        topic: topic.ref,
        ackDeadlineSeconds: .literal(60),
        delivery: .pushConfig(
          .new(pushEndpoint: .literal('https://app.example.com/push')),
        ),
      ),
    );
    // …
    addConstant('ordersTopicName', .ref(topic.name));
    addOutput('orders_topic_id', topic.id);
  }
}

Type-safe

The compiler reviews your infrastructure

Mistakes that usually surface halfway through a deploy show up as red squiggles while you type.

  • topic: orders.ref

    References, not strings

    An argument that names another resource takes that resource's ref. Pass the wrong kind and it does not compile.

  • ingress: .all

    Enums for fixed values

    Every fixed value set is an enum, written with Dart 3.10 dot shorthands and completed by your IDE.

  • delivery: .pushConfig(...)

    Sealed choices

    When a block takes exactly one of several shapes, it is a sealed type. Pick a variant; the compiler rules out the rest.

  • for (final env in Env.values)

    Just Dart

    Loops, functions, enums for environments, packages and tests. A Stack is a final class, not a template language.

Writing arguments Why TerraDart

Already have Terraform?

Bring it over one resource at a time

terradart migrate turns an existing tree into a Dart package. Size it first, migrate it, and keep shipping while you move the rest.

One Stack per module
Each directory becomes a Dart class; local modules get a typed wrapper.
Nothing is dropped
Blocks no factory covers yet stay beside the generated JSON, verbatim, with the reason.
No changes
Resource addresses are kept, so the plan against your existing state is empty.
Environments merge
--merge-envs folds dev and prod into one Stack and an Env enum.
Migrating from HCL
Terminal
$ terradart migrate --report --dir infra
$ terradart migrate --dir infra --out infra_dart
$ cd infra_dart && terradart plan

No changes. Your infrastructure matches the configuration.

Start with one Stack.

Install the command, add a provider package, and apply.

dart pub global activate terradart_cli