SeaORM 2.0: a Rust ORM for web services, and where its dense entity format helps
🐚 A powerful relational ORM for Rust
At a glance
- What is it?
- SeaORM is an async ORM for Rust built on sqlx, aimed at REST, GraphQL and gRPC backends. Version 2.0 adds a dense entity format, a smart entity loader and an entity-first schema workflow.
- Who is it for?
- Adopt SeaORM if you are building a Rust web service against Postgres, MySQL, MariaDB or SQLite and want generated entities plus a migration crate in the same workspace. It is the wrong tool for a small CLI that issues two queries, or for a team that wants to write every statement by hand.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 28, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What SeaORM is for, and who it is not for
SeaORM solves a specific problem: writing database access in Rust without hand-rolling row mapping, connection handling and relationship loading for every table. The crate describes itself as "an async & dynamic ORM for Rust", and the README frames the target audience as people "building web services in Rust". The repository ships integration examples for Actix, Axum, GraphQL, jsonrpsee, Loco, Poem, Rocket, Salvo and Tonic, which tells you the intended shape of the consumer: an HTTP or RPC service with several related tables.
The project is not trying to be a query builder for scripts. If your program runs one query and exits, the entity derivation, the code generation step and the migration crate are overhead you will pay for and never use. The same applies if your team has a strong preference for writing SQL by hand and reviewing it in pull requests; SeaORM supports raw SQL, but the value it adds sits in the typed layer above it.
Two design choices set expectations. First, it is async, so it assumes a Tokio-style runtime. Second, it is dynamic, which means queries are assembled at runtime rather than checked at compile time. That is a trade-off you should accept deliberately: compile-time query checking is what a different class of Rust database library offers, and SeaORM does not.
Dense entities, the smart loader and the N+1 problem
The mechanism that matters most in 2.0 is the entity format. Entity files can be generated from an existing database with sea-orm-cli, and the README shows output produced with `--entity-format dense`, which the release notes mark as new in 2.0. In the dense format, a struct field carries both the column and the relation: `#[sea_orm(has_one)] pub profile: HasOne<super::profile::Entity>` sits next to `pub name: String` in the same model. Relations are declared with attributes such as `#[sea_orm(belongs_to, from = "user_id", to = "id")]` and `#[sea_orm(has_many, via = "post_tag")]` for a many-to-many relation through a junction table.
The second mechanism is the entity loader. According to the README, it "intelligently uses join for 1-1 and data loader for 1-N relations, eliminating the N+1 problem even when performing nested queries". The documented call is `user::Entity::load().filter_by_id(42).with(profile::Entity).with((post::Entity, tag::Entity)).one(db)`. The README states that three queries run underneath: a join across user and profile filtered by id, a select on post with an `IN` list of user ids, and a select on tag joined to post_tag with an `IN` list of post ids. That is the whole trick. One-to-one becomes a join, one-to-many becomes a batched second query, and nesting does not multiply the query count per row.
A third mechanism is nested persistence. `ActiveModel::builder()` lets you set a name, set an email, attach a profile, add a post and add a tag to that post, then call `.save(db)`. The README says SeaORM determines dependencies and inserts or deletes objects in the correct order, and that this requires the 2.0 dense entity format. So the dense format is not cosmetic: the persistence builder depends on it.
Where this becomes a real constraint is the loader's fixed strategy. If your query pattern wants a single join for a one-to-many relation, or a lateral join, the loader will not produce it. You get the strategy the README documents, or you drop to raw SQL.
Installing SeaORM and running a first query
The README does not print a copy-paste install block; it points to the documentation site and to the `examples/quickstart` directory for a single-file example. What the repository does show is the package name and the minimum toolchain. The Cargo manifest declares `name = "sea-orm"` and `rust-version = "1.94.0"`, so check your toolchain before you start. The crate is published on crates.io, and the CLI is a separate crate, `sea-orm-cli`, released in lockstep with the library (both at 2.0.3 on 2026-09-13).
Add the library to an existing Cargo project. The feature names come from the docs.rs metadata list in the manifest, which enables `sqlx-all`, `mock`, `proxy`, `rbac`, `schema-sync`, `tracing-spans`, `runtime-tokio-native-tls` and others for documentation builds.
[dependencies]
sea-orm = { version = "2.0.3", features = ["sqlx-all", "runtime-tokio-native-tls"] }Install the CLI, which the README names as the tool that generates entity files from an existing database.
cargo install sea-orm-cliGenerate entities from a running database. The README names the `--entity-format dense` flag as the 2.0 way to produce the compact output shown in the entity example, so run the generator against your schema and inspect the files it writes before wiring them into your service.
sea-orm-cli generate entity --entity-format denseWith entities in place, the first real query mirrors the loader example. The README's version filters by primary key and eager-loads a one-to-one and a one-to-many relation in one call, then unwraps the optional result.
let smart_user = user::Entity::load()
.filter_by_id(42)
.with(profile::Entity)
.with((post::Entity, tag::Entity))
.one(db)
.await?
.unwrap();What you should see is a populated model where `profile` is a loaded `HasOne` and `posts` is a loaded `HasMany` containing its own loaded `tags`. If the relations are declared wrongly in the generated entity, this is where it surfaces.
Entity-first schema sync and what it costs you
SeaORM has always offered a migration system for creating tables, altering schemas and seeding data. Version 2.0 adds what the README calls a first-class Entity First Workflow: you define new entities or add columns to existing ones, and SeaORM detects the changes and creates the new tables, columns, unique keys and foreign keys. The documented call is one line, `db.get_schema_registry("my_crate::entity::*").sync(db).await;`, and the README notes that SeaORM resolves foreign key dependencies and creates tables in topological order.
That is a meaningful shift in where schema truth lives. In the schema-first direction, migrations are the source of truth and entities are generated from the database. In the entity-first direction, Rust structs are the source of truth and the database follows. Both are supported, and the README presents the choice as yours.
The cost is worth stating plainly. The schema-sync path requires the `entity-registry` and `schema-sync` feature flags, so it is opt-in and pulls in the `inventory` crate for registration. It also means schema changes are driven by code that must compile and run, which is convenient in development and awkward in a production release process where a reviewed SQL migration is the artifact your DBA signs off on. The README does not document a rollback path for a sync that drops or narrows a column. Treat that as the boundary: use schema sync where you can recreate the database, and use the migration crate where you cannot.
Raw SQL, transactions and the escape hatches
The README positions raw SQL as the exception rather than the rule: "Let SeaORM handle 95% of your transactional queries. For the remaining cases that are too complex to express," and the sentence is cut off in the excerpt. What is verifiable is that the crate exposes an ergonomic raw SQL interface alongside the typed one, and that the query builder covers filters, pagination and nested queries, which the README lists under the feature summary.
Transactions are part of the same surface. The README groups transactional queries under the 95 percent figure, so the intended workflow is to express normal reads and writes through the builder and reserve hand-written SQL for the cases the builder cannot reach. This is a reasonable split, but it has a consequence for review: a codebase can drift into two styles, and the typed half will not catch a mistake in the raw half.
One thing the README does not spell out is how the dynamic query layer behaves when a filter is applied to a column that does not exist in the generated entity. Because queries are assembled at runtime, that class of error is not a compile error. It is a runtime error, and it belongs in your test suite rather than in your type checker.
SeaORM against Diesel and sqlx
The most common comparison for SeaORM is Diesel. The difference is architectural rather than cosmetic. Diesel builds queries through a type-level DSL, so the shape of a query is checked by the compiler; SeaORM assembles queries at runtime, which is why the crate describes itself as dynamic. Diesel's approach catches malformed queries earlier and costs compile time and a steeper learning curve. SeaORM's approach compiles faster and reads closer to the object graph in your head, and it moves query errors to runtime. Neither is strictly better; they fail at different moments.
The second comparison is sqlx, which SeaORM depends on. sqlx is a lower-level async toolkit: you write SQL, and it maps rows to structs, with macros that can verify queries against a live database at compile time. SeaORM sits above that layer and adds entity derivation, relation loading, migrations and the nested persistence builder. Choosing sqlx means you keep full control of every statement and write the mapping yourself. Choosing SeaORM means you accept the loader's join-versus-data-loader strategy and the dense entity format in exchange for not writing that mapping.
A practical way to decide: if your service has many related tables and you want the object graph to be the unit of work, SeaORM removes real boilerplate. If your queries are few, long and hand-tuned, sqlx gives you the same runtime with nothing in between.
Maintenance, releases and licence
The repository is not archived, and the last push was on 2026-09-20. The most recent release listed is 2.0.3 on 2026-09-13, with `[email protected]` published minutes later the same day, and 2.0.2 before it on 2026-08-12. The library and its CLI move together, so an upgrade is a two-part operation: bump the `sea-orm` dependency and reinstall or update `sea-orm-cli`, then regenerate entities if the entity format or generator output changed. The README's own note that `--entity-format dense` is new in 2.0 is the kind of change that makes regeneration necessary rather than optional.
The workspace also contains `sea-orm-macros`, `sea-orm-codegen` and `sea-orm-arrow` as members, and the repository layout shows `sea-orm-migration`, `sea-orm-cli`, `sea-orm-rocket` and `sea-orm-sync` at the top level. That is a multi-crate project, so a version bump in the core crate can ripple through the codegen and macro crates in the same release train.
On licensing: the Cargo manifest declares `license = "MIT OR Apache-2.0"`, and the repository carries both `LICENSE-APACHE` and `LICENSE-MIT` files. The GitHub repository metadata says Apache-2.0, which is one half of the dual grant. Dual MIT and Apache-2.0 is the common Rust convention and is generally permissive, but the choice between the two matters for patent language and for how you record attribution. That is a question for your own legal review, not something this article can settle.
The minimum supported Rust version is 1.94.0 and the crate uses edition 2024. If your CI pins an older toolchain, the upgrade to 2.0.3 is blocked until you move it.
Editorial conclusion
Adopt SeaORM if you are building a Rust web service against Postgres, MySQL, MariaDB or SQLite and want generated entities plus a migration crate in the same workspace. It is the wrong tool for a small CLI that issues two queries, or for a team that wants to write every statement by hand. Before committing, check the minimum supported Rust version against your toolchain, read the docs for the sea-orm-cli generate subcommand, and confirm that the schema-sync feature flag is documented for the database you run.
Frequently asked questions
What is SeaORM?
SeaORM is an async ORM for Rust, described in its Cargo manifest as "an async & dynamic ORM for Rust" and in the README as a tool for building web services. It provides generated entities, relation loading, migrations and a nested persistence builder on top of sqlx.
How does SeaORM compare with Diesel?
Diesel checks query shape through a type-level DSL at compile time, while SeaORM assembles queries at runtime, which is why the crate calls itself dynamic. The practical difference is when errors appear: Diesel at compile time, SeaORM at runtime, in exchange for faster compiles and a lower learning curve.
How does SeaORM compare with sqlx?
SeaORM is built on sqlx and adds entity derivation, the smart entity loader, migrations and the ActiveModel builder above it. sqlx itself is the lower layer: you write SQL and handle row mapping, with macros that can verify queries against a live database.
Is there a PostgreSQL ORM written in Rust?
Yes. SeaORM lists postgres among its keywords and its docs.rs metadata enables the sqlx-all feature set, and the README's loader example shows the query patterns SeaORM generates for relational backends. Postgres, MySQL, MariaDB and SQLite all appear in the project's topics.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/seaql-sea-orm)