# SeaORM 2.0.0

SeaORM 2.0 is the first stable release of the 2.x line. It reworks how entities,
relations, and ActiveModels are defined and used, adds an entity-first workflow,
introduces role-based access control, and brings the library onto SeaQuery 1.0 and
SQLx 0.9.

The full, itemized changelog for the 2.0 series (all release candidates included)
is in [CHANGELOG.md](../CHANGELOG.md#200---2026-07-19).

## Highlights

### New entity format

Relations are now declared directly on the `Model` struct with `#[sea_orm::model]`,
replacing the separate `Relation` enum and `Related` impls.

```rust
#[sea_orm::model]
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "user")]
pub struct Model {
    #[sea_orm(primary_key)]
    pub id: i32,
    pub name: String,
    #[sea_orm(has_one)]
    pub profile: HasOne<super::profile::Entity>,
    #[sea_orm(has_many)]
    pub posts: HasMany<super::post::Entity>,
}
```

See the [new entity format walk-through](https://www.sea-ql.org/blog/2025-10-20-sea-orm-2.0/).

### `BelongsTo` relation type with compile-time cardinality

A `belongs_to` relation can be typed `BelongsTo<Entity>` (required) or
`BelongsTo<Option<Entity>>` (optional), encoding the foreign-key cardinality in the
type and paired with the write-side `ActiveBelongsTo`. The macro validates the type
against the nullability of the `from` columns at compile time. `BelongsTo` is the
recommended type for `belongs_to`; the legacy `HasOne<Entity>` field type remains
supported for backward compatibility. (https://github.com/SeaQL/sea-orm/pull/3118)

### Strongly-typed columns

Filter with the typed `COLUMN` constant for compile-time type safety, alongside the
existing `Column` enum.

```rust
user::Entity::find().filter(user::COLUMN.name.contains("Bob"))
```

See [strongly-typed columns](https://www.sea-ql.org/blog/2025-11-11-sea-orm-2.0/).

### Nested ActiveModel

Build and persist an entity together with its related rows in one expression, and
push has-many children onto a loaded model.

```rust
let bob = user::ActiveModel::builder()
    .set_name("Bob")
    .set_email("bob@sea-ql.org")
    .set_profile(profile::ActiveModel::builder().set_picture("Tennis"))
    .insert(db)
    .await?;
```

See [nested ActiveModel](https://www.sea-ql.org/blog/2025-11-25-sea-orm-2.0/).

### Entity Loader

Load an entity together with its relations, including nested relations, in a single
call.

```rust
let user = user::Entity::load()
    .filter_by_id(12)
    .with(profile::Entity)
    .with((post::Entity, comment::Entity))
    .one(db)
    .await?;
```

### Entity-first workflow

Create tables directly from entity definitions via the schema registry, without
writing a migration first.

```rust
db.get_schema_registry("my_crate::*").sync(db).await?;
```

See the [entity-first workflow](https://www.sea-ql.org/blog/2025-10-30-sea-orm-2.0/).

### Role-Based Access Control

A table-scoped, hierarchical RBAC engine with a query auditor and a
`RestrictedConnection` that implements `ConnectionTrait` and enforces permissions on
all Entity operations (including complex joins, insert-select, and CTE queries).
(https://github.com/SeaQL/sea-orm/pull/2683)

### Overhauled `insert_many`

`insert_many` no longer shares a helper struct with single insert. Panic-prone APIs
were removed, empty input returns `None` / `vec![]` on exec, and the new `InsertMany`
helper exposes `last_insert_id: Option<Value>`. (https://github.com/SeaQL/sea-orm/pull/2628)

### Synchronous SeaORM

The `sea-orm-sync` crate provides a synchronous SeaORM backed by `rusqlite`, mirroring
the async API with async/await stripped away.

## Upgrading from 1.x

Follow the [1.0 to 2.0 migration guide](https://www.sea-ql.org/blog/2026-01-12-sea-orm-2.0/)
and the [2.0 walk-through](https://www.sea-ql.org/blog/2025-12-05-sea-orm-2.0/).

Notable breaking changes to be aware of:

- Expression methods like `.eq()`, `.like()`, `.contains()` now require
  `use sea_orm::ExprTrait;` in scope. Also read
  [SeaQuery's breaking changes](https://github.com/SeaQL/sea-query/blob/master/CHANGELOG.md#breaking-changes).
- `execute` / `query_one` / `query_all` / `stream` now take a SeaQuery statement; the
  raw-SQL variants are `execute_raw` / `query_one_raw` / `query_all_raw` / `stream_raw`.
- PostgreSQL auto-increment columns now use `GENERATED BY DEFAULT AS IDENTITY` instead
  of `serial`; opt back in with `option-postgres-use-serial` if needed.
- SQLite maps both `Integer` and `BigInteger` to `integer`.
- `DeriveValueType` now also derives `NotU8`, `IntoActiveValue`, and `TryFromU64`;
  remove any manual implementations to avoid conflicts.
- Removed the `runtime-actix` feature alias (use `runtime-tokio`); removed
  `DeriveCustomColumn` and `default_as_str`.

## Dependencies

- SeaQuery 1.0
- SQLx 0.9
- sea-schema 0.18
