Database support
Define your data models once, and Serverpod generates the Dart classes, the database schema, the migrations, and a type-safe query API on top of Postgres or SQLite. No SQL to hand-write, no mapping layer to keep in sync.
One language across your stack
On a typical stack, your Flutter app is written in Dart, and your database is queried in SQL, with a mapping layer in between that you maintain by hand. When the schema changes, you update the models, queries, and migrations separately, and hope they still agree. That gap between the two sides is a common source of bugs.
Serverpod removes that gap. You describe your data once, and the framework generates the table, the migration, and the query API from it. Because queries are type-checked when you compile, referencing a column that does not exist or comparing the wrong type fails to build, rather than failing in production. The same types carry through all the way from the database row to your Flutter widget.
From model to widget in three steps
1Define your model
Add the table key to a model file, and Serverpod manages the id column for you, as an int or a UUID.
class: Company
table: company
fields:
name: String
2Run serverpod start
serverpod start runs your server, watches your models, and regenerates the Dart class, the table definition, and the full API as you save. When the schema changes, press M in its terminal to create the migration and A to apply it.
serverpod start
3Use the typed result in Flutter
The Company class exists on the server and in your Flutter app, with no serialization code in between.
// Server: an endpoint that queries the database.
class CompanyEndpoint extends Endpoint {
Future<List<Company>> list(Session session) =>
Company.db.find(session);
}
// Flutter: the same typed Company, straight from the database row.
var companies = await client.company.list();
Query with a type-safe API
Filters are built from typed column descriptors and checked at compile time. Logical operators (& for and, | for or, ~ for not), like and ilike, between, inSet, comparisons, and count are all supported.
var alice = await User.db.find(
session,
where: (t) => t.name.equals('Alice') & (t.age > 25),
);
Relations without the boilerplate
Serverpod supports one-to-one, one-to-many, many-to-many, and self-relations, along with referential actions. You can filter across a relation and aggregate over it (any, every, none, count) in the same type-safe expression.
var powerBuyers = await User.db.find(
session,
where: (t) => t.orders.count((o) => o.itemType.equals('book')) > 3,
);
Migrations that track your schema
serverpod create-migration compares your models against the last migration and writes the SQL needed to move the database forward. It stops when a change would risk data loss unless you pass a force flag, and repair migrations bring a database that has drifted back in line.
serverpod create-migration
Transactions, indexes, and optimization
Group operations into atomic transactions with configurable isolation levels and savepoints. You declare indexes in the model file, from a plain unique constraint up to GIN, vector (HNSW and IVFFLAT), and spatial indexes.
await session.db.transaction((transaction) async {
await Company.db.insertRow(session, company, transaction: transaction);
await Employee.db.insertRow(session, employee, transaction: transaction);
});
Vector and geospatial search
The same typed query API also covers pgvector similarity search (L2, cosine, inner product, and more) and PostGIS geography types, with spatial operators such as distanceWithin, intersects, and contains.
var similar = await Document.db.find(
session,
where: (t) => t.embedding.distanceCosine(queryVector) < 0.5,
orderBy: (t) => t.embedding.distanceCosine(queryVector),
limit: 10,
);
The same database, on the device
Mark a model with database: client and your Flutter app gets a local SQLite database with the same ORM interface as the server, including relations and transactions. Postgres handles production-scale workloads on the server; SQLite handles on-device and lightweight workloads.
var session = await client.createSession(path);
var pending = await Task.db.find(
session,
where: (t) => t.done.equals(false),
);
Everything included
any, every, none, count)Why Serverpod
Works with
Postgres PostGIS pgvector SQLite
Frequently asked questions
Does Serverpod work with Postgres?
Yes. Postgres is the default backend, and a local Postgres instance is set up for you when you create a new project.
Can I use the database offline in my Flutter app?
Yes. Your Flutter app gets a local SQLite database with the same query API as the server, so it reads and writes fully offline and on device.
Is the ORM type-safe?
Yes. Queries are built from typed column descriptors and checked at compile time, so a wrong column or type is caught before the code runs.
How do database migrations work?
serverpod create-migration compares your models to the last migration and generates the SQL to update the schema. It guards against accidental data loss, and repair migrations resync a database that has drifted.
Can I write raw SQL when I need to?
Yes. Alongside the generated API, you can run explicit SQL queries through the session's database access.
Does it support vector search for AI and embeddings?
Yes, on Postgres. Vector fields support pgvector similarity search (L2, cosine, inner product, and more) with HNSW and IVFFLAT indexes. Vector search is not available on the SQLite backend.
Does it support geospatial queries?
Yes. PostGIS geography types support spatial filters and ordering, including distance, intersection, and containment.
Does it create foreign keys and relations for me?
Yes. Declaring a relation in a model generates the foreign keys and keeps the related data in sync, with support for referential actions.