Generate type-safe Dart models from your Supabase tables automatically! This tool helps you port projects to Flutter (from FlutterFlow) or create new Flutter projects with full type safety and Supabase integration.
- Automatically generates Dart classes from Supabase tables
- Creates type-safe models with full IDE support
- Supports complex relationships and nested structures
- Compatible with Flutter and Flutter Flow paradigms
- Generates getters and setters for all fields
- Supabase project with tables
- Dart/Flutter development environment
- Environment configuration file (
env.dart
)
- Add the generator to your project's root directory
- Create an
env.dart
file with your Supabase credentials:
const supabaseUrl = 'YOUR_SUPABASE_URL';
const supabaseAnonKey = 'YOUR_SUPABASE_ANON_KEY';
- Run the generation script:
# On Unix-like systems
./generate_models.sh
# On Windows
generate_models.bat
- Setup your preferred auth in Supabase.dart, as well as your keys
String _kSupabaseUrl = 'XXXXXXXXXXXXXX';
String _kSupabaseAnonKey = 'XXXXXXXXXXXXXX';
...
if (session != null) {
print('[Supabase] User ID: ${session.user.id}');
// Create auth user wrapper and update app state
// Initialize your auth here
//final authUser = FlutterAppSupabaseUser(session.user);
//AppStateNotifier.instance.update(authUser);
}
You can optionally use the auth classes I've provided in lib/auth but it is not required.
The generator will create strongly-typed models like this:
class UsersTable extends SupabaseTable<UsersRow> {
@override
String get tableName => 'users';
@override
UsersRow createRow(Map<String, dynamic> data) => UsersRow(data);
}
class UsersRow extends SupabaseDataRow {
UsersRow(super.data);
@override
SupabaseTable get table => UsersTable();
String get id => getField<String>('id')!;
set id(String value) => setField<String>('id', value);
String? get name => getField<String>('name');
set name(String? value) => setField<String>('name', value);
DateTime get createdAt => getField<DateTime>('created_at')!;
set createdAt(DateTime value) => setField<DateTime>('created_at', value);
}
final userAccountsTable = UserAccountsTable();
// Fetch a single user
final users = await userAccountsTable.queryRows(
queryFn: (q) => q.eq('id', 123),
limit: 1,
);
if (users.isNotEmpty) {
final user = users.first;
// Access typed properties
print(user.email);
print(user.accName);
print(user.phoneNumber);
print(user.createdAt);
}
// Fetch multiple users
final activeUsers = await userAccountsTable.queryRows(
queryFn: (q) => q
.eq('is_active', true)
.order('email'),
);
// Work with typed objects
for (final user in activeUsers) {
print('User ${user.id}:');
print('- Email: ${user.email}');
print('- Name: ${user.accName ?? "No name set"}');
print('- Phone: ${user.phoneNumber ?? "No phone set"}');
print('- Created: ${user.createdAt}');
}
// Query with complex conditions
final recentUsers = await userAccountsTable.queryRows(
queryFn: (q) => q
.gte('created_at', DateTime.now().subtract(Duration(days: 7)))
.ilike('email', '%@gmail.com')
.order('created_at', ascending: false),
);
final userAccountsTable = UserAccountsTable();
// Create new record
final newUser = await userAccountsTable.insert({
'email': '[email protected]',
'acc_name': 'John Doe',
'phone_number': '+1234567890',
});
// The returned object is already typed
print(newUser.email);
print(newUser.accName);
final userAccountsTable = UserAccountsTable();
// Update by query
await userAccountsTable.update(
data: {'acc_name': 'Jane Doe'},
matchingRows: (q) => q.eq('id', 123),
);
// Update with return value
final updatedUsers = await userAccountsTable.update(
data: {'is_active': true},
matchingRows: (q) => q.in_('id', [1, 2, 3]),
returnRows: true,
);
final userAccountsTable = UserAccountsTable();
// Delete single record
await userAccountsTable.delete(
matchingRows: (q) => q.eq('id', 123),
);
// Delete with return value
final deletedUsers = await userAccountsTable.delete(
matchingRows: (q) => q.eq('is_active', false),
returnRows: true,
);
// Get a pilot and their documents
final pilotsTable = PilotsTable();
final documentsTable = DocumentsTable();
// Get pilot
final pilots = await pilotsTable.queryRows(
queryFn: (q) => q.eq('id', pilotId),
);
final pilot = pilots.firstOrNull;
// Get related documents
if (pilot != null) {
final documents = await documentsTable.queryRows(
queryFn: (q) => q.eq('pilot_id', pilot.id),
);
}
- Ensure your Supabase tables have proper primary keys defined
- All generated models are null-safe
- Custom column types are supported through type converters
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.