Open a database topic

Choose an entrypoint

Define a Record schema, generate its client, choose a database entrypoint, and use the generated table getters. Each query belongs to that database view. Migrations have a separate, fixed history for the chosen engine.

For an application, import its generated client and one database entrypoint:

import 'package:my_app/schema.orm.dart';
import 'package:orm/sqlite.dart';

final db = await sqlite(const SqliteOptions.file('app.sqlite'));
try {
  final tasks = await db.task.where((t) => t.done.eq(false)).get();
  print(tasks);
} finally {
  await db.close();
}

The database owns its resources until close() completes. PostgreSQL's postgres(options) creates a lazy pool synchronously; it first connects when an operation acquires a connection. The other connection helpers return futures that complete after opening and validating their connection.

Imports

SQLite uses sqlite.dart on native platforms and the web. SqliteOptions.memory() is portable. persistent(name, nativePath: ...) uses the native path or the named browser database. Flutter bundles its browser resources automatically; see SQLite Web setup. Platform transports are internal.

Module or use Import Purpose
Values package:orm/values.dart Codecs, decimal/temporal/domain value contracts
Driver contracts package:orm/driver.dart SQL commands, raw results, connections and capabilities
Database adapters package:orm/drivers/sqlite.dart, postgres.dart, mysql.dart, mariadb.dart under drivers/ Raw drivers without ORM models
Raw SQL execution package:orm/runtime.dart SqlDatabase, sessions, transactions, cursors and observations
Physical schema package:orm/schema_model.dart Columns, keys, constraints and indexes without execution
Typed SQL package:orm/sql.dart Query construction, typed projections and SqlBuilder without a connection runtime
ORM execution package:orm/orm.dart Database binds typed queries to SqlDatabase and adds subscriptions
Schema declaration package:orm/schema.dart Record model definitions, columns, keys and relationships
Application convenience Generated schema.orm.dart plus package:orm/sqlite.dart, postgres.dart, mysql.dart or mariadb.dart Model types, table getters and the chosen adapter
Migration or snapshot package:orm/migrate.dart Physical history, steps, checksums and execution against SqlDatabase
Project CLI package:orm/cli.dart OrmConfig, static history and unified project commands
Migration-only executable package:orm/migrate_cli.dart Programmatic commands with a static history and raw connection factory
Generation scripts package:orm/generate.dart Analyze declarations and write derived Dart files
build_runner configuration package:orm/builder.dart Builder factories only

Generated clients define immutable row classes and typed table getters. Model handles and unrelated application helpers remain in the source schema library. Types used inside rows, such as a custom ID or enum, remain owned by their defining library. Import that library when constructing those values. Use import prefixes for multiple generated clients.

These are real dependency boundaries within one package. sql.dart does not import the connection runtime or ORM, and runtime.dart does not import the query builder. migrate.dart uses raw sessions and physical metadata without model declarations or generated query code. None of these runtime/schema imports pull in the analyzer, build system or a concrete database adapter. sqlite.dart selects its platform implementation; each server engine has its own options. MySQL and MariaDB entrypoints each expose their own driver and connection options. Import public library paths; files under lib/src/ are implementation details. The single package still declares tooling dependencies for its CLI and builders; pub get resolves them. This import boundary avoids runtime initialization and compilation dependencies, not the dependency-download cost of one package.

For raw SQL, construct SqlDatabase(await SqliteDriver.open(options)) from runtime.dart and drivers/sqlite.dart. Add typed execution later with Database.fromSql(raw); it uses the same runtime rather than another pool. db.sql exposes that runtime to migrations and catalog operations.

For offline compilation, import sql.dart and a generated client:

final command = SqlBuilder(SqlDialect.postgres)
    .user.where((u) => u.email.eq('seven@example.com'))
    .select((u) => u.id)
    .compile();
// command.sql and separately bound command.parameters; no connection opened.

Executing an unbound builder fails with QUERY.UNBOUND. Generated table getters and named SQL bindings target QueryContext, which both SqlBuilder and the ORM implement. Field and result types remain static; SQL capabilities and a missing named-query engine variant are checked against the chosen context before I/O.

Three kinds of Dart files

schema.dart is the current declaration. schema.orm.dart and schema.snapshot.dart are reproducible outputs; regenerate them after editing the declaration. migrations/m0001_*.dart is reviewed history. It contains its own frozen physical schema and fixed fingerprint, independent of the latest models.

Generation validates the structure and types. A migration history selects SQLite, PostgreSQL, MySQL or MariaDB and validates that engine's capabilities. A SQLite-only declaration may use a virtual indexed column; PostgreSQL restrictions should not prevent its generation. The selected engine still rejects unsupported DDL before applying it. Changing a connection URL does not convert a migration history.

Shape, identity and selection

Each generated model class is its complete row type. A User and an unrelated class with identical fields remain different Dart types. Record projections remain structural. Two models can declare identical field shapes while keeping distinct row types and SQL table identities. table.alias() creates a new occurrence for joins and self joins. Capturing a field from an unrelated query is a scope error.

select((u) => u.email) returns List<String> from get(). .row composes SQL expressions into a positional Record; .map(...) shapes decoded values into a named Record or DTO. A Dart mapper does not become a SQL expression. Runtime fields({...}) selection deliberately returns Map<String, Object?>.

alias.optional(selection) checks that alias's presence. It does not prove another left-joined alias exists. Give each optional alias its own guard, or use nullable expressions. Relationships follow the same rule: one() can be absent, required() checks presence, and many() loads a collection. Relationship execution documents joins, batched queries and consistency boundaries.

Preparing and executing

where, select, orderBy, take, skip and map return new descriptions. Repeated where adds predicates; orderBy, take and skip replace their own settings. compile()/inspect() perform no I/O. A stream acquires a cursor when listened to. Choose the read operation by the expected result:

Operation Empty result One row Multiple rows
get() Empty list List of one List of all
first() Cardinality error The row First row
firstOrNull() null The row First row
single() Cardinality error The row Cardinality error
singleOrNull() null The row Cardinality error

A selected SQL NULL still counts as a row. Strict reads return that null when their result type allows it. Use a Record projection if an optional scalar read must distinguish an absent row from a row containing null.

Generated create(...) and patch(...) execute immediately. insert(...), insertMany(...), update(...) and delete() prepare mutations; call execute() or returning(...).get(). Prepared inserts evaluate client defaults once when constructed. Re-executing the prepared insert reuses those values.

Change.keep() omits an assignment, .set(null) writes SQL NULL to a nullable field, and .defaultValue() requests the database default. An empty update is an error. RETURNING returns scalar SQL projections; query relationships afterward. For statement deadlines or cancellation, use the prepared mutation's execution options. Defaults and execution specify the limits.

Connection and transaction ownership

The root SqlDatabase owns its driver; Database uses that runtime. session borrows one connection; transaction borrows one connection and commits or rolls back the callback's writes. Only use the callback's tx to construct that transaction's queries. Subqueries, CTEs and UNION operands must all use the same view. Use savepoint for a recoverable nested unit; borrowed views expire at callback completion.

Pass tx into helper functions. Calling the captured root db separately inside the callback requests separate work: a pool may run it outside the transaction, and a single-connection SQLite driver may wait on the callback's own lease. Composition checks reject mixed query descriptions; they do not rewrite unrelated root calls into transaction calls.

await db.transaction((tx) async {
  final user = await tx.user.create(email: 'seven@example.com');
  await tx.post.create(
    authorId: user.id,
    title: 'First post',
    createdAt: DateTime.now(),
  );
});

Await every operation before returning from the callback. Batch inserts use one transaction across chunks. A normal query with batched relationships reuses its connection but does not automatically create a snapshot transaction. Choose an appropriate explicit transaction when a consistent multi-query snapshot matters. Query observations expose actual SQL and transaction statements.

Raw SQL remains explicit: bind values in SqlCommand or typed sql expressions, declare affected/read tables for subscriptions, and use reviewed migration steps for schema changes. The ORM does not infer an object graph, external writes, automatic transaction replay or destructive renames.

Custom codecs, manual Table definitions and Driver implementations are explicit extension contracts. Their authors supply the correct storage mapping and native behavior. Dart types cannot prove that a live external schema still matches those definitions; use catalog verification when that boundary matters.

Libraries

mariadb Open a database
Typed MariaDB queries with MariaDB-specific capabilities and history.
mysql Open a database
Typed MySQL queries with explicit connection and transaction ownership.
orm Open a database
Typed queries, explicit transactions and change subscriptions.
postgres Open a database
Typed PostgreSQL queries backed by an owned connection pool.
sqlite Open a database
Typed SQLite queries on native Dart, Flutter and the web.

Classes

Database<B extends Backend> Open a database
Typed queries and change notifications over an owned SQL runtime.