supafreeze 1.0.6
supafreeze: ^1.0.6 copied to clipboard
Generate Freezed models from Supabase database schema automatically. Supports incremental generation, smart caching, and build_runner integration.
supafreeze #
Generate Freezed models from your Supabase database schema automatically.
Features #
- Fetches table schema directly from Supabase PostgreSQL database
- Generates type-safe Freezed models with
fromJson/toJson - CLI tool for syncing schema changes
- Smart caching - only regenerates when DB schema changes
- Relation embedding - auto-detects foreign keys and embeds related models
- Flexible configuration - supports
.env, environment variables, and dart-define - Automatic PostgreSQL to Dart type mapping
- Handles nullable fields, primary keys, and default values
- snake_case to camelCase conversion with
@JsonKeyannotations - Include/exclude specific tables
Installation #
Add to your pubspec.yaml:
dependencies:
freezed_annotation: ^2.4.0
json_annotation: ^4.8.0
dev_dependencies:
supafreeze: ^1.0.4
build_runner: ^2.4.0
freezed: ^2.4.0
json_serializable: ^6.7.0
Quick Start #
1. Create configuration files #
Create supafreeze.yaml in your project root:
# supafreeze.yaml (commit this to git)
url: ${SUPABASE_DATA_API_URL}
secret_key: ${SUPABASE_SECRET_KEY}
output: lib/models
schema: public
Create .env file for your credentials:
# .env (add to .gitignore!)
SUPABASE_DATA_API_URL=https://your-project.supabase.co
SUPABASE_SECRET_KEY=your-secret-key
2. Generate models #
# Step 1: Fetch schema from Supabase and generate .supafreeze.dart files
dart run supafreeze:supafreeze
# Step 2: Run freezed/json_serializable to generate .freezed.dart and .g.dart files
dart run build_runner build
That's it! supafreeze will:
- Connect to your Supabase database
- Fetch the schema for all tables
- Generate
*.supafreeze.dartfiles inlib/models/ - Then build_runner generates
.freezed.dartand.g.dartfiles
3. Use the generated models #
import 'package:your_app/models/users.supafreeze.dart';
final user = Users(id: '123', name: 'John', createdAt: DateTime.now());
final json = user.toJson();
4. When you modify the database schema #
Whenever you add, modify, or delete tables in Supabase:
# Sync latest schema from Supabase
dart run supafreeze:supafreeze
# Regenerate freezed/json_serializable code
dart run build_runner build
CLI Tool #
The CLI tool is the primary way to sync your models with Supabase:
# Sync schema from Supabase (only generates changed models)
dart run supafreeze:supafreeze
# Force regenerate ALL models (ignores cache)
dart run supafreeze:supafreeze --force
dart run supafreeze:supafreeze -f
How it works #
- Fetches schema from Supabase via the OpenAPI spec endpoint
- Compares with locally cached schema to detect changes
- Generates only the modified
*.supafreeze.dartfiles - Updates the local cache
After running the CLI, always run dart run build_runner build to generate the .freezed.dart and .g.dart files.
Why use the CLI tool? #
- Explicit control: You decide when to sync with Supabase
- Fast: Only regenerates changed models
- Works offline: Use cached schema when Supabase is unavailable
- CI/CD friendly: Commit generated files and skip API calls in CI
Configuration #
supafreeze.yaml #
# Required
url: ${SUPABASE_DATA_API_URL} # Supabase Data API URL
secret_key: ${SUPABASE_SECRET_KEY} # Supabase service role key
# Optional
output: lib/models # Output directory (default: lib/models)
schema: public # PostgreSQL schema (default: public)
fetch: always # Fetch mode: always | if_no_cache | never
generate_barrel: false # Generate models.dart barrel file
embed_relations: false # Auto-embed related models via FK
# Table filtering (use one or the other, not both)
include: # Only generate these tables
- users
- posts
exclude: # Skip these tables
- _migrations
- audit_logs
Variable Resolution #
supafreeze supports multiple ways to reference sensitive values:
# Auto-resolve: checks dart-define > .env > environment variables
url: ${SUPABASE_DATA_API_URL}
# Explicit environment variable
url: $env{SUPABASE_DATA_API_URL}
# Explicit .env file variable
secret_key: $dotenv{SUPABASE_SECRET_KEY}
Priority order (highest first):
- dart-define (
--dart-define=SUPABASE_SECRET_KEY=xxx) .envfile- Environment variables
Fetch Mode #
| Mode | Description |
|---|---|
always |
Always fetch from database (default) |
if_no_cache |
Only fetch if no cache exists |
never |
Never fetch, always use cache (offline mode) |
GitHub Actions #
Recommended: Commit generated files #
The simplest approach is to commit the generated *.supafreeze.dart files to your repository:
# .github/workflows/build.yml
name: Build
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.x'
- run: flutter pub get
# Generated .supafreeze.dart files are already in the repo
# Just run build_runner for freezed/json_serializable
- name: Run build_runner
run: dart run build_runner build --delete-conflicting-outputs
- name: Run tests
run: flutter test
Local workflow:
# When you change the database schema:
dart run supafreeze:supafreeze
dart run build_runner build
git add lib/models/
git commit -m "Update models from Supabase schema"
git push
Alternative: Fetch from Supabase in CI #
If you want CI to fetch the latest schema:
# .github/workflows/build.yml
name: Build
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.x'
- run: flutter pub get
# Fetch schema from Supabase
- name: Sync Supabase schema
run: dart run supafreeze:supafreeze
env:
SUPABASE_DATA_API_URL: ${{ secrets.SUPABASE_DATA_API_URL }}
SUPABASE_SECRET_KEY: ${{ secrets.SUPABASE_SECRET_KEY }}
# Generate freezed/json_serializable code
- name: Run build_runner
run: dart run build_runner build --delete-conflicting-outputs
- name: Run tests
run: flutter test
Required secrets:
SUPABASE_DATA_API_URL: Your Supabase Data API URLSUPABASE_SECRET_KEY: Your Supabase service role key
Alternative: Use cached schema in CI #
If you don't want to expose Supabase credentials in CI:
# .github/workflows/build.yml
name: Build
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.x'
# Cache supafreeze schema
- name: Cache supafreeze schema
uses: actions/cache@v4
with:
path: .dart_tool/supafreeze
key: supafreeze-${{ hashFiles('lib/models/*.supafreeze.dart') }}
restore-keys: |
supafreeze-
- run: flutter pub get
# Use cached schema (no API call)
- name: Sync schema (from cache)
run: dart run supafreeze:supafreeze
env:
SUPAFREEZE_FETCH: never
- name: Run build_runner
run: dart run build_runner build --delete-conflicting-outputs
- name: Run tests
run: flutter test
Setup:
- Run
dart run supafreeze:supafreezelocally first - Commit the
.dart_tool/supafreeze/directory, or let the CI cache it after the first successful run
To commit the cache:
# .gitignore
.dart_tool/
!.dart_tool/supafreeze/
Relation Embedding #
supafreeze can auto-detect foreign key relationships from *_id columns and embed related models. Disabled by default.
Enable relation embedding #
url: ${SUPABASE_DATA_API_URL}
secret_key: ${SUPABASE_SECRET_KEY}
output: lib/models
embed_relations: true
Generated output #
When enabled, if a posts table has a user_id column and a users table exists:
// posts.supafreeze.dart (auto-generated)
import 'users.supafreeze.dart';
@freezed
class Posts with _$Posts {
const factory Posts({
required String id,
required String title,
@JsonKey(name: 'user_id') required String userId,
Users? user, // ← Auto-embedded from user_id FK
}) = _Posts;
}
Using with Supabase queries #
// Fetch posts with embedded user
final response = await supabase
.from('posts')
.select('*, user:users(*)');
final posts = (response as List)
.map((json) => Posts.fromJson(json))
.toList();
// Access the embedded user
print(posts.first.user?.name);
Disable specific relations #
embed_relations: true
relations:
posts:
user: false # Disable user embedding for posts table
comments:
author: false # Disable author embedding for comments table
Generated Output #
For a table like:
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
email TEXT,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
supafreeze generates:
lib/models/users.supafreeze.dart
// GENERATED CODE - DO NOT MODIFY BY HAND
import 'package:freezed_annotation/freezed_annotation.dart';
part 'users.supafreeze.freezed.dart';
part 'users.supafreeze.g.dart';
@freezed
class Users with _$Users {
const factory Users({
required String id,
required String name,
String? email,
@Default(true) bool isActive,
@JsonKey(name: 'created_at') required DateTime createdAt,
}) = _Users;
factory Users.fromJson(Map<String, dynamic> json) => _$UsersFromJson(json);
}
Type Mapping #
| PostgreSQL | Dart |
|---|---|
int2, int4, int8, serial |
int |
float4, float8, numeric |
double |
text, varchar, char |
String |
bool, boolean |
bool |
timestamp, timestamptz, date |
DateTime |
uuid |
String |
json, jsonb |
Map<String, dynamic> |
text[], int4[], etc. |
List<T> |
Caching #
supafreeze stores per-table hashes in .dart_tool/supafreeze/:
table_hashes.json- SHA256 hash for each tableschema_cache.json- Full schema data
When you run dart run supafreeze:supafreeze:
- Fetches current schema from Supabase
- Computes hash for each table
- Compares with cached hashes
- Generates only changed/new tables
- Removes files for deleted tables
- Updates cache
Troubleshooting #
Schema fetch fails #
If supafreeze can't connect to Supabase, it will attempt to use the cached schema. If no cache exists, generation will fail.
# Use cached schema when offline
SUPAFREEZE_FETCH=never dart run supafreeze:supafreeze
Force regeneration #
# Force regenerate all models
dart run supafreeze:supafreeze --force
dart run build_runner build
Or delete the cache:
rm -rf .dart_tool/supafreeze
dart run supafreeze:supafreeze
dart run build_runner build
Environment variables not working #
Make sure your .env file:
- Is in the project root directory
- Has no spaces around
=signs - Values with spaces are quoted:
KEY="value with spaces"
Models out of sync with Supabase #
Always run the CLI tool when you change your database schema:
dart run supafreeze:supafreeze
dart run build_runner build
License #
MIT