Defining Models
Models in Ormed are Dart classes annotated with @OrmModel that map to database tables.
Naming Conventions
By default, Ormed follows standard database naming conventions:
- Tables: Pluralized
snake_caseversion of the class name.User→usersUserRole→user_roles
- Columns:
snake_caseversion of the field name.emailAddress→email_addresscreatedAt→created_at
You can override these defaults using the table property on @OrmModel and the columnName property on @OrmField.
- Snippets show model definitions and generated usage in isolation.
- Run
dart run build_runner buildafter creating/updating models to generate$Model,OrmDefinition, and DTOs.
Basic Model
// #region intro-model
import 'package:ormed/ormed.dart';
part 'user.orm.dart';
(table: 'users')
class User extends Model<User> {
const User({
required this.id,
required this.email,
this.name,
this.createdAt,
});
(isPrimaryKey: true, autoIncrement: true)
final int id;
final String email;
final String? name;
final DateTime? createdAt;
}
// #endregion intro-model
After running dart run build_runner build, this generates:
$User- Tracked model class with change trackingUserOrmDefinition- Model metadata and static helpers$UserPartial- Partial entity for projectionsUserInsertDto/UserUpdateDto- Data transfer objects
Model Annotation Options
- Notes
- Example
Use options to control table naming, timestamps, soft deletes, and other model-level behavior.
import 'package:ormed/ormed.dart';
part 'admin.orm.dart';
(
table: 'admins',
hidden: ['password'], // Fields hidden from serialization
fillable: ['email'], // Fields that can be mass-assigned
guarded: ['id'], // Fields protected from mass-assignment
casts: {'createdAt': 'datetime'},
)
class Admin extends Model<Admin> {
const Admin({
required this.id,
required this.email,
this.password,
this.createdAt,
});
(isPrimaryKey: true, autoIncrement: true)
final int id;
final String email;
final String? password;
final DateTime? createdAt;
}
Attribute metadata (fillable/guarded/hidden/visible/casts)
Ormed can enforce mass-assignment rules and control what gets serialized.
- Notes
- Casts
fillable/guardedare used bymodel.fill(...)andmodel.forceFill(...).hidden/visibleaffectmodel.toArray()/model.toJson().casts/@OrmField(cast: ...)pick codecs for database + serialization.
See Models → Model Attributes (mass assignment + serialization) and Models → Casting (casts + custom codecs).
Field Annotations
Primary Key
- Notes
- Examples
Primary key configuration affects inserts, updates, and how models are identified when syncing relations.
(table: 'items')
class ItemWithIntPK extends Model<ItemWithIntPK> {
const ItemWithIntPK({required this.id});
(isPrimaryKey: true)
final int id;
}
(table: 'auto_items')
class ItemWithAutoIncrement extends Model<ItemWithAutoIncrement> {
const ItemWithAutoIncrement({required this.id});
// Auto-increment (default for integer PKs)
(isPrimaryKey: true, autoIncrement: true)
final int id;
}
(table: 'uuid_items')
class ItemWithUuidPK extends Model<ItemWithUuidPK> {
const ItemWithUuidPK({required this.id});
// UUID primary key
(isPrimaryKey: true)
final String id;
}
Column Options
(table: 'contacts')
class Contact extends Model<Contact> {
const Contact({
required this.id,
required this.email,
this.active = true,
this.name,
});
(isPrimaryKey: true, autoIncrement: true)
final int id;
// Custom column name
(columnName: 'user_email')
final String email;
// Default value in SQL
(defaultValueSql: '1')
final bool active;
// Nullable field
final String? name; // Automatically nullable in DB
}
Custom Codecs
For complex types, use value codecs:
(table: 'documents')
class Document extends Model<Document> {
const Document({required this.id, this.metadata});
(isPrimaryKey: true, autoIncrement: true)
final int id;
(codec: JsonMapCodec)
final Map<String, Object?>? metadata;
}
class JsonMapCodec extends ValueCodec<Map<String, Object?>> {
const JsonMapCodec();
Map<String, Object?> decode(Object? value) {
if (value == null) return {};
if (value is String) return jsonDecode(value) as Map<String, Object?>;
return value as Map<String, Object?>;
}
Object? encode(Map<String, Object?> value) => jsonEncode(value);
}
Generated Code
Tracked Model ($User)
The generated $User class is the "tracked" version of your model with:
- Change tracking for dirty fields
- Relationship accessors
- Model lifecycle methods
void trackedModelUsage() {
// The generated class
final user = $User(id: 1, email: 'john@example.com');
// Modify and track changes
user.setAttribute('name', 'John Doe');
print(user.isDirty); // true
print(user.dirtyFields); // ['name']
}
Definition (UserOrmDefinition)
Provides static helpers and model metadata:
void definitionUsage() {
// Access the model definition
final definition = UserOrmDefinition.definition;
print(definition.tableName); // 'users'
print(definition.primaryKey.name); // 'id'
}
Partial Entity ($UserPartial)
For projecting specific columns:
Future<void> partialEntityUsage(DataSource dataSource) async {
final partial = await dataSource.query<$User>().select([
'id',
'email',
]).firstPartial();
print(partial?.id); // Available
print(partial?.email); // Available
// partial.name is not available (not selected)
}
DTOs
Data transfer objects for insert/update operations:
Future<void> dtoUsage(DataSource dataSource) async {
// Insert DTO
final insertDto = UserInsertDto(email: 'new@example.com');
await dataSource.repo<$User>().insert(insertDto);
// Update DTO
final updateDto = UserUpdateDto(name: 'New Name');
await dataSource.repo<$User>().update(updateDto, where: {'id': 1});
}
Best Practices
- Use const constructors - Helps with immutability and tree-shaking
- Define all fields as final - Models are immutable by design
- Use nullable types for optional fields - Clearer intent
- Keep models focused - One model per table
- Use DTOs for partial updates - More explicit than tracked models