Skip to content

Appwrite

terradart_appwrite wraps the official appwrite/appwrite provider — every resource and data source at its pinned version. A Flutter team on Appwrite can declare its project, databases, storage, functions and messaging in Dart, next to the app that uses them.

pubspec.yaml
dependencies:
terradart_core: ^0.35.0
terradart_appwrite: ^0.35.0

Appwrite currently needs Terraform on your PATH. The appwrite/appwrite provider is published to the Terraform registry only, and OpenTofu cannot install it from there. A Stack that uses it runs on terraform from PATH without being asked; pin it in pubspec.yaml (terradart init --provider appwrite writes this) or pass --engine terraform:

pubspec.yaml
terradart:
engine: terraform

With only OpenTofu available, or with --engine tofu, terradart plan, apply, destroy and outputs stop before init and say so (The engine).

AppwriteProvider takes the endpoint and the project or organization, but no API key, so credentials never enter the synthesized JSON, and synth needs none. terradart plan and terradart apply authenticate with APPWRITE_API_KEY (project resources) or APPWRITE_ORGANIZATION_API_KEY (organization resources). With them in place, terradart apply synthesizes the Stack and applies it.

lib/backend_stack.dart
import 'package:terradart_appwrite/provider.dart';
import 'package:terradart_appwrite/storage.dart';
import 'package:terradart_appwrite/tablesdb.dart';
final class BackendStack extends Stack {
BackendStack()
: super(
providers: [
const AppwriteProvider(
endpoint: 'https://cloud.appwrite.io/v1',
projectId: 'my-project',
),
],
appExports: AppExports('lib/generated/backend_stack.app.dart'),
) {
final db = add(AppwriteTablesdb('main', name: .literal('main')));
final notes = add(AppwriteTablesdbTable(
'notes',
databaseId: db.ref, // only an AppwriteTablesdb fits here
name: .literal('notes'),
rowSecurity: .literal(true),
));
final uploads = add(AppwriteStorageBucket(
'uploads',
name: .literal('uploads'),
fileSecurity: .literal(true),
maximumFileSize: .literal(10485760),
));
addOutput('database_id', db.id);
addOutput('notes_table_id', notes.id);
addOutput('uploads_bucket_id', uploads.id);
}
}

Appwrite assigns the IDs on create, so they are outputs, each with a typed getter on BackendStackOutputs (databaseId, notesTableId, uploadsBucketId). For the Flutter app, add addDartDefineOutput() to the Stack: terradart apply (or terradart outputs) then writes the define file flutter build --dart-define-from-file reads, and const BackendStackOutputs.fromDartDefine() reads the IDs in the app — see Outputs in client apps. A script reads the same define file at run time with BackendStackOutputs.fromEnvironment(...).

Who may read or change a bucket, file, table or row is a list of AppwritePermission (from package:terradart_appwrite/auth.dart), one per action and role, so a misspelled role does not compile:

lib/uploads_stack.dart
import 'package:terradart_appwrite/auth.dart';
import 'package:terradart_appwrite/provider.dart';
import 'package:terradart_appwrite/storage.dart';
final class UploadsStack extends Stack {
UploadsStack()
: super(
providers: [
const AppwriteProvider(
endpoint: 'https://cloud.appwrite.io/v1',
projectId: 'my-project',
),
],
) {
final editors = add(
AppwriteAuthTeam('editors', name: .literal('Editors')),
);
add(AppwriteStorageBucket(
'uploads',
name: .literal('uploads'),
permissions: .literal([
.read(.any),
.create(.users(verified: true)),
.write(.team(editors.ref, role: 'owner')),
]),
));
}
}

It synthesizes to the provider’s strings (read("any"), write("team:${appwrite_auth_team.editors.id}/owner")). The roles are .any, .guests, .users(), .user(user.ref), .team(team.ref), .member(id) and .label(name); .literal('read("any")') takes a permission string as it is.

Inputs with a fixed value set are Dart enums, taken from the provider’s validators. A sensitive input, such as a backup provider’s secret key, is best passed as a Terraform variable (final key = variable<String>('backup_secret_key', sensitive: true), then secretKey: key) so its value arrives at apply time rather than in the Dart source.