Models
Models are where database rows become application concepts. In Ormed, a model is a Dart class mapped to a table; code generation adds the tracked types, metadata, DTOs, relations, and query helpers that keep that class useful in an application.
Should this be a model?
Create a model when the data has a stable role in your application and you want to use it in more than one place:
- The fields should be checked by the Dart compiler.
- The entity has relationships, lifecycle rules, or reusable behavior.
- Multiple reads and writes should share the same mapping and input rules.
Stay with direct database access when you are exploring a schema, running a one-off report, or querying a table where a domain model would only add ceremony. You can introduce a model later without changing the underlying database.
The smallest useful model
Start with the table mapping and the fields your application actually uses:
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;
}
Run the generator after creating or changing the model:
dart run build_runner build --delete-conflicting-outputs
The source model stays readable and intentional. Generated files hold the repetitive runtime machinery.
What gets generated
For a User model, the generated API gives you distinct types for distinct
jobs:
| Generated API | Use it for |
|---|---|
$User | Tracked model instances returned from queries and used in updates |
UserOrmDefinition | Metadata, codecs, and lower-level model registration |
UserInsertDto / UserUpdateDto | Explicit data passed into create and update operations |
UserPartial | Queries that select only part of a model |
Users.query() / Users.where(...) | Typed static query entry points |
You do not need to use every generated type directly. Start with the query and repository APIs, then reach for a DTO or partial projection when the workflow benefits from being explicit.
Add model behavior when the problem appears
| If you need to… | Read… |
|---|---|
| Map a class, key, table, or column | Defining Models |
| Control input and serialized output | Attributes |
| Convert database values to Dart types | Casting |
| Connect entities and load related data | Relationships |
Manage createdAt, updatedAt, or deletedAt | Timestamps and Soft Deletes |
| Reuse query constraints or model helpers | Scopes and Model Methods |
| Observe or guard lifecycle changes | Events |
| Handle a database-specific type or column | Driver Overrides |
This keeps a model focused: add a capability because the application needs it, not because every model has to demonstrate every feature.
A practical rollout
- Map the stable shape. Define the primary key, persisted fields, and any table or column overrides.
- Generate and verify. Run the builder, then make one read and one write through the generated API.
- Add boundaries deliberately. Introduce casts, fill rules, relations, timestamps, or soft deletes only when their behavior is part of the domain.
- Keep queries readable. Use a repository for model-oriented CRUD and the Query Builder for filtering, joins, projections, and aggregates.
- Test the behavior that can surprise you. Check generated mappings, relation loading, serialization, and lifecycle hooks at the edges of the application.
Continue from here
- Defining Models — create the first annotated model.
- Querying Data — use generated models in reads and writes.
- Migrations Overview — keep the database schema aligned.
- Code Generation — configure and verify the builder.