Models
Models in JAO are Dart classes annotated with @Model(). Each field is annotated with a field type that maps to a database column.
Basic Model
import 'package:jao/jao.dart';
part 'user.g.dart';
@Model()
class User {
@AutoField()
late int id;
@CharField(maxLength: 100)
late String name;
@EmailField()
late String email;
@BooleanField(defaultValue: true)
late bool isActive;
@DateTimeField(autoNowAdd: true)
late DateTime createdAt;
}
Info
Always include part 'filename.g.dart'; to enable code generation. The generated file contains the manager class and field accessors.
Generated Code
After running dart run build_runner build, JAO generates:
Users— Manager class for database operationsUsers.$— Field accessor for type-safe queriesUsers.schema— Schema definition for migrationsUsers.toRow()— Convert model to MapUsers.fromRow()— Convert Map to model
Model Annotation Options
@Model(
tableName: 'custom_users', // Custom table name (default: snake_case of class name)
abstract: false, // Abstract model (won't create table)
ordering: ['name'], // Default ordering for queries
uniqueTogether: [['email', 'tenantId']], // Multi-column unique constraints
indexTogether: [['status', 'createdAt']], // Multi-column indexes
appLabel: 'users', // Namespace for the model
)
class User {
// ...
}
Model Options
| Option | Type | Description |
|---|---|---|
tableName |
String |
Custom table name (default: snake_case of class) |
abstract |
bool |
If true, no table is created (for inheritance) |
ordering |
List<String> |
Default ORDER BY for queries |
uniqueTogether |
List<List<String>> |
Multi-column unique constraints |
indexTogether |
List<List<String>> |
Multi-column indexes |
appLabel |
String |
Namespace/category for the model |
Multi-Column Unique Constraint
Ensure combinations of fields are unique:
@Model(
uniqueTogether: [
['email', 'tenantId'], // Same email can exist in different tenants
['slug', 'categoryId'], // Slug unique within category
],
)
class Article {
@AutoField()
late int id;
@EmailField()
late String email;
@IntegerField()
late int tenantId;
@CharField(maxLength: 100)
late String slug;
@ForeignKey(Category, onDelete: OnDelete.cascade)
late int categoryId;
}
Multi-Column Index
Create composite indexes for common query patterns:
@Model(
indexTogether: [
['status', 'createdAt'], // For filtering by status + date range
['authorId', 'isPublished'], // For author's published posts
],
)
class Post {
// ...
}
Default Ordering
Set default query ordering:
@Model(
ordering: ['-createdAt', 'name'], // Prefix with - for descending
)
class Post {
// ...
}
// Queries will automatically order by createdAt DESC, name ASC
final posts = await Posts.objects.all().toList();
Field Options
All field types support these common options:
@CharField(
maxLength: 100,
nullable: true, // Allow NULL values
unique: true, // Add UNIQUE constraint
index: true, // Create index on this column
defaultValue: 'N/A', // Default value
dbColumn: 'custom_name', // Custom column name in database
)
late String? name;
Nullable Fields
Use nullable Dart types with nullable: true:
@TextField(nullable: true)
late String? bio;
Default Values
Provide default values that are set when not explicitly provided:
@BooleanField(defaultValue: true)
late bool isActive;
@IntegerField(defaultValue: 0)
late int viewCount;
Unique Constraints
Enforce uniqueness at the database level:
@EmailField(unique: true)
late String email;
Database Indexes
Add indexes for frequently queried columns:
@CharField(maxLength: 100, index: true)
late String slug;
Relationships
Foreign Key
Define a many-to-one relationship:
@Model()
class Post {
@AutoField()
late int id;
@CharField(maxLength: 200)
late String title;
@ForeignKey(Author, onDelete: OnDelete.cascade)
late int authorId;
}
OnDelete Options
| Option | Description |
|---|---|
OnDelete.cascade |
Delete related records |
OnDelete.setNull |
Set foreign key to NULL |
OnDelete.restrict |
Prevent deletion if references exist |
OnDelete.noAction |
Database default behavior |
One-to-One
Define a one-to-one relationship:
@Model()
class UserProfile {
@AutoField()
late int id;
@OneToOneField(User, onDelete: OnDelete.cascade)
late int userId;
@TextField(nullable: true)
late String? bio;
}
Auto Timestamps
JAO provides automatic timestamp handling:
@Model()
class Post {
@AutoField()
late int id;
@CharField(maxLength: 200)
late String title;
// Set automatically when record is created
@DateTimeField(autoNowAdd: true)
late DateTime createdAt;
// Updated automatically on every save
@DateTimeField(autoNow: true)
late DateTime updatedAt;
}
Primary Keys
Auto-increment Primary Key
@AutoField()
late int id;
Big Auto-increment Primary Key
For tables that may exceed 2 billion rows:
@BigAutoField()
late int id;
UUID Primary Key
@UuidPrimaryKey()
late String id;
Complete Example
import 'package:jao/jao.dart';
part 'models.g.dart';
@Model()
class Author {
@AutoField()
late int id;
@CharField(maxLength: 100)
late String name;
@EmailField(unique: true)
late String email;
@IntegerField(min: 0)
late int age;
@BooleanField(defaultValue: true)
late bool isActive;
@TextField(nullable: true)
late String? bio;
@DateTimeField(autoNowAdd: true)
late DateTime createdAt;
@DateTimeField(autoNow: true)
late DateTime updatedAt;
}
@Model()
class Post {
@AutoField()
late int id;
@CharField(maxLength: 200)
late String title;
@TextField()
late String content;
@CharField(maxLength: 100, index: true)
late String slug;
@ForeignKey(Author, onDelete: OnDelete.cascade)
late int authorId;
@BooleanField(defaultValue: false)
late bool isPublished;
@IntegerField(defaultValue: 0)
late int viewCount;
@DateTimeField(autoNowAdd: true)
late DateTime createdAt;
@DateTimeField(autoNow: true)
late DateTime updatedAt;
}
@Model()
class Tag {
@AutoField()
late int id;
@CharField(maxLength: 50, unique: true)
late String name;
@CharField(maxLength: 50, index: true)
late String slug;
}
Best Practices
⚠️ Warning
Always run dart run build_runner build after modifying models to regenerate the query classes.
- Use meaningful names — Model names should be singular nouns (Author, not Authors)
- Add indexes — Index columns used in WHERE clauses or ORDER BY
- Use appropriate field types — Use
EmailFieldfor emails,UuidFieldfor UUIDs, etc. - Set nullable explicitly — Be explicit about which fields can be NULL
- Use auto timestamps — Use
autoNowAddandautoNowfor created/updated tracking
Next Steps
- Learn the Query API to fetch and manipulate data
- See all available Field Types
- Manage schema changes with Migrations