ormed
A strongly-typed ORM (Object-Relational Mapping) core for Dart, inspired by Eloquent (Laravel), GORM, SQLAlchemy, and ActiveRecord. Combines compile-time code generation with runtime flexibility for type-safe database operations.
Features
- Annotations:
@OrmModel,@OrmField,@OrmRelation,@OrmScope,@OrmEventto describe tables, columns, relationships, and behaviors - Code Generation: Emits
ModelDefinition,FieldDefinition,ModelCodec, DTOs, and tracked model classes - Query Builder: Fluent, type-safe queries with filtering, ordering, pagination, aggregates, and eager loading
- Relationships: HasOne, HasMany, BelongsTo, ManyToMany, and polymorphic relations (MorphOne, MorphMany)
- Migrations: Schema builder with Laravel-style table blueprints and driver-specific overrides
- Value Codecs: Custom type serialization with driver-scoped codec registries
- Soft Deletes: Built-in support with
withTrashed(),onlyTrashed()scopes - Event System: Model lifecycle events (Creating, Created, Updating, Updated, Deleting, Deleted)
- Testing:
TestDatabaseManagerwith database isolation strategies
Installation
dependencies:
ormed: any
ormed_sqlite: any # Or ormed_postgres, ormed_mysql
dev_dependencies:
ormed_cli: any
build_runner: ^2.4.0
Getting Started
The recommended way to use Ormed is via the ormed CLI, which manages migrations, seeders, and project scaffolding.
1. Install the CLI
You can install the CLI globally (recommended for smaller dependency trees) or add it to your dev_dependencies:
# Global installation
dart pub global activate ormed_cli
Note: Adding
ormed_clitodev_dependenciesensures version parity with your project but will include all database drivers and their dependencies in yourpubspec.lock.
2. Initialize the Project
Scaffold the configuration and directory structure:
ormed init
(Or use dart run ormed_cli:ormed init if not installed globally).
This creates ormed.yaml and the lib/src/database directory.
3. Define a Model
Create a model file (e.g., lib/src/models/user.dart):
import 'package:ormed/ormed.dart';
part 'user.orm.dart';
@OrmModel()
class User extends Model<User> {
final String name;
final String email;
User({required this.name, required this.email});
}
3. Generate ORM Code
Run build_runner to generate model definitions and tracked classes:
dart run build_runner build
4. Create and Run Migrations
Generate a migration for your model:
dart run ormed_cli:ormed make --name create_users_table --create --table users
Edit the generated migration in lib/src/database/migrations/ to add columns:
void up(SchemaBuilder schema) {
schema.create('users', (table) {
table.id();
table.string('name');
table.string('email').unique();
table.timestamps();
});
}
Apply the migration:
dart run ormed_cli:ormed migrate
5. Seed the Database
Create a seeder:
dart run ormed_cli:ormed make --name UserSeeder --seeder
Add data in lib/src/database/seeders/user_seeder.dart:
Future<void> run() async {
await seed<User>([
{'name': 'John Doe', 'email': 'john@example.com'},
]);
}
Run the seeders:
dart run ormed_cli:ormed seed
6. Bootstrap and Use
In your application code, use bootstrapOrm() to register all generated models and metadata automatically:
import 'package:ormed/ormed.dart';
import 'package:ormed_sqlite/ormed_sqlite.dart';
import 'orm_registry.g.dart'; // Generated by ormed
void main() async {
// 1. Bootstrap the registry
final registry = bootstrapOrm();
// 2. Initialize DataSource
final ds = DataSource(DataSourceOptions(
driver: SqliteDriverAdapter.file('database.sqlite'),
registry: registry,
));
await ds.init();
// 3. Query your models
final users = await ds.query<User>().get();
print('Found ${users.length} users');
}
Migrations
Ormed supports two migration formats, which can be mixed in the same project. The migration runner is format-agnostic, allowing you to use the best tool for each change.
Dart Migrations (Default)
Type-safe migrations using a fluent SchemaBuilder. Best for most use cases.
dart run ormed_cli:ormed make --name add_bio_to_users
SQL Migrations
Raw .sql files for complex schema changes or when porting existing SQL scripts.
dart run ormed_cli:ormed make --name add_bio_to_users --format sql
This creates a directory with up.sql and down.sql files.
Simultaneous Support
You can have Dart and SQL migrations in the same project. The runner executes them in chronological order based on their timestamps, regardless of format. This flexibility allows you to use Dart for standard schema changes and SQL for database-specific features or complex optimizations.
Annotations
@OrmModel
By default, Ormed infers the table name by pluralizing and snake_casing the class name (e.g., User -> users, UserRole -> user_roles).
@OrmModel(
table: 'users',
schema: 'public', // Optional schema/namespace
softDeletes: true, // Enable soft deletes
primaryKey: 'id', // Alternative to @OrmField(isPrimaryKey: true)
fillable: ['email', 'name'], // Mass assignment whitelist
guarded: ['role'], // Mass assignment blacklist
hidden: ['password'], // Hidden from serialization
connection: 'analytics', // Connection name override
)
@OrmField
By default, Ormed infers the column name by snake_casing the field name (e.g., emailAddress -> email_address).
@OrmField(
columnName: 'user_email', // Custom column name
isPrimaryKey: true,
autoIncrement: true,
isNullable: true,
isUnique: true,
isIndexed: true,
codec: JsonMapCodec, // Custom value codec
columnType: ColumnType.text, // Override column type
defaultValueSql: 'NOW()', // SQL default expression
driverOverrides: {...}, // Per-driver customizations
)
@OrmRelation
// One-to-One (foreign key on related table)
@OrmRelation.hasOne(related: Profile, foreignKey: 'user_id')
final Profile? profile;
// One-to-Many
@OrmRelation.hasMany(related: Post, foreignKey: 'author_id')
final List<Post> posts;
// Inverse relation
@OrmRelation.belongsTo(related: User, foreignKey: 'user_id')
final User author;
// Many-to-Many
@OrmRelation.manyToMany(
related: Tag,
pivot: 'post_tags',
foreignPivotKey: 'post_id',
relatedPivotKey: 'tag_id',
)
final List<Tag> tags;
// Polymorphic
@OrmRelation.morphMany(related: Comment, morphName: 'commentable')
final List<Comment> comments;
Query Builder
final posts = await ds.query<$Post>()
// Filtering
.whereEquals('status', 'published')
.whereIn('category_id', [1, 2, 3])
.whereNull('deleted_at')
.whereBetween('views', 100, 1000)
.whereHas('comments', (q) => q.whereEquals('approved', true))
// Eager loading
.with_(['author', 'tags'])
.withCount('comments')
// Ordering & Pagination
.orderByDesc('created_at')
.limit(20)
.offset(40)
// Execute
.get();
// Aggregates
final count = await ds.query<$Post>().count();
final avgViews = await ds.query<$Post>().avg('views');
// Pagination
final page = await ds.query<$Post>().paginate(page: 2, perPage: 15);
Migrations
class CreateUsersTable extends Migration {
@override
void up(SchemaBuilder schema) {
schema.create('users', (table) {
table.id(); // bigIncrements primary key
table.string('email').unique();
table.string('name').nullable();
table.boolean('active').defaultValue(true);
table.timestamps(); // created_at, updated_at
table.softDeletes(); // deleted_at
});
}
@override
void down(SchemaBuilder schema) {
schema.drop('users');
}
}
Column Types
| Method | Description |
|---|---|
id() |
Auto-incrementing big integer primary key |
string(name, length?) |
VARCHAR column |
text(name) |
TEXT column |
integer(name) / bigInteger(name) |
Integer columns |
decimal(name, precision?, scale?) |
Decimal column |
boolean(name) |
Boolean column |
dateTime(name) / timestamp(name) |
DateTime columns |
json(name) / jsonb(name) |
JSON columns |
binary(name) |
Binary/BLOB column |
enum_(name, values) |
Enum column |
Column Modifiers
table.string('locale')
.nullable()
.unique()
.defaultValue('en')
.comment('User locale')
.driverType('postgres', ColumnType.jsonb()) // Per-driver type
.driverDefault('postgres', "'{}'::jsonb"); // Per-driver default
Value Codecs
Custom type serialization for database ↔ Dart conversion:
class UuidCodec extends ValueCodec<UuidValue> {
const UuidCodec();
@override
UuidValue? decode(Object? value) =>
value == null ? null : UuidValue.fromString(value as String);
@override
Object? encode(UuidValue? value) => value?.uuid;
}
// Register globally
ValueCodecRegistry.instance.registerCodecFor(UuidCodec, const UuidCodec());
// Or per-driver
ValueCodecRegistry.instance.forDriver('postgres')
.registerCodec(key: 'UuidValue', codec: const PostgresUuidCodec());
Repository Operations
final repo = ds.repo<$User>();
// Create
await repo.insert($UserInsertDto(email: 'new@example.com'));
await repo.insertMany([dto1, dto2, dto3]);
// Read
final user = await repo.find(1);
final all = await repo.all();
final first = await repo.first();
// Update (accepts DTOs, models, or maps)
await repo.update(dto, where: {'id': 1});
await repo.update(dto, where: (q) => q.whereEquals('email', 'old@example.com'));
// Delete
await repo.delete(model);
await repo.deleteByIds([1, 2, 3]);
// Upsert
await repo.upsert(dto, uniqueBy: ['email']);
Transactions
await ds.transaction(() async {
await ds.repo<$User>().insert(userDto);
await ds.repo<$Profile>().insert(profileDto);
// Automatically rolls back on exception
});
Naming Conventions
ormed follows a "convention over configuration" approach for database names:
- Tables: Inferred from the model class name using plural snake_case.
User→usersUserProfile→user_profiles
- Columns: Inferred from field names using snake_case.
emailAddress→email_addresscreatedAt→created_at
You can override these using the @OrmModel and @OrmField annotations:
@OrmModel(tableName: 'legacy_users')
class User extends Model<User> {
@OrmField(columnName: 'user_email')
final String email;
}
Database Drivers
Ormed requires a database driver to connect to your database. Choose the driver that matches your database:
SQLite (ormed_sqlite)
Best for: Local development, mobile apps, embedded databases, testing.
dependencies:
ormed_sqlite: any
import 'package:ormed_sqlite/ormed_sqlite.dart';
// In-memory (great for testing)
final adapter = SqliteDriverAdapter.inMemory();
// File-based
final adapter = SqliteDriverAdapter.file('app.sqlite');
Features: In-memory & file databases, JSON1 extension, FTS5 full-text search, window functions, R*Tree spatial indexes.
PostgreSQL (ormed_postgres)
Best for: Production applications, complex queries, advanced data types.
dependencies:
ormed_postgres: any
import 'package:ormed_postgres/ormed_postgres.dart';
final adapter = PostgresDriverAdapter.fromUrl(
'postgres://user:pass@localhost:5432/mydb',
);
Features: UUID, JSONB, arrays, geometric types, full-text search (tsvector), range types, RETURNING clause, connection pooling.
MySQL/MariaDB (ormed_mysql)
Best for: Existing MySQL infrastructure, WordPress/Laravel migrations, web hosting.
dependencies:
ormed_mysql: any
import 'package:ormed_mysql/ormed_mysql.dart';
final adapter = MySqlDriverAdapter.fromUrl(
'mysql://user:pass@localhost:3306/mydb',
);
Features: MySQL 8.0+, MariaDB 10.5+, JSON columns, ENUM/SET types, spatial types, Laravel-compatible configuration.
Related Packages
| Package | Description |
|---|---|
ormed_sqlite |
SQLite driver adapter |
ormed_postgres |
PostgreSQL driver adapter |
ormed_mysql |
MySQL/MariaDB driver adapter |
ormed_cli |
CLI tool for migrations and scaffolding |
Examples
See the example/ directory and packages/orm_playground for comprehensive usage examples.