Skip to main content

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_case version of the class name.
    • Userusers
    • UserRoleuser_roles
  • Columns: snake_case version of the field name.
    • emailAddressemail_address
    • createdAtcreated_at

You can override these defaults using the table property on @OrmModel and the columnName property on @OrmField.

Snippet context
  • Snippets show model definitions and generated usage in isolation.
  • Run dart run build_runner build after 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 tracking
  • UserOrmDefinition - Model metadata and static helpers
  • $UserPartial - Partial entity for projections
  • UserInsertDto / UserUpdateDto - Data transfer objects

Model Annotation Options

Use options to control table naming, timestamps, soft deletes, and other model-level behavior.

Attribute metadata (fillable/guarded/hidden/visible/casts)

Ormed can enforce mass-assignment rules and control what gets serialized.

  • fillable / guarded are used by model.fill(...) and model.forceFill(...).

  • hidden / visible affect model.toArray() / model.toJson().

  • casts / @OrmField(cast: ...) pick codecs for database + serialization.

Field Annotations

Primary Key

Primary key configuration affects inserts, updates, and how models are identified when syncing relations.

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

  1. Use const constructors - Helps with immutability and tree-shaking
  2. Define all fields as final - Models are immutable by design
  3. Use nullable types for optional fields - Clearer intent
  4. Keep models focused - One model per table
  5. Use DTOs for partial updates - More explicit than tracked models