Skip to main content
Version: Next

Working with models

Models are Yaml files used to define serializable classes in Serverpod. They are used to generate Dart code for the server and client, and, if a database table is defined, to generate database code for the server.

Using regular .yaml files within lib/src/models is supported, but it is recommended to use .spy.yaml (.spy stands for "Serverpod YAML"). Using this file type allows placing the model files anywhere in your servers lib directory and enables syntax highlighting provided by the Serverpod Extension for VS Code.

The files are analyzed by the Serverpod CLI when generating the project and creating migrations.

Run serverpod generate to generate dart classes from the model files.

Class

class: Company
fields:
name: String
foundedDate: DateTime?
employees: List<Employee>

Supported types are bool, int, double, String, Duration, DateTime, ByteData, UuidValue, Uri, BigInt, Vector, HalfVector, SparseVector, Bit and other serializable classes, exceptions and enums. You can also use Lists, Maps and Sets of the supported types, just make sure to specify the types. All supported types can also be used inside Records. Null safety is supported. Once your classes are generated, you can use them as parameters or return types to endpoint methods.

Required fields

Nullable types are supported by adding a ? after the type. E.g., String? or List<Employee>?. The optional required keyword makes the generated field a required constructor parameter.

class: Person
fields:
name: String
nickname: String?, required
age: int?

In the example above, nickname will be a required constructor parameter.

Limiting visibility of a generated class

By default, generated code for your serializable objects is available both on the server and the client. You may want to have the code on the server side only. E.g., if the serializable object is connected to a database table containing private information.

To make a serializable class generated only on the server side, set the serverOnly property to true.

class: MyPrivateClass
serverOnly: true
fields:
hiddenSecretKey: String

It is also possible to set a scope on a per-field basis. By default all fields are visible to both the server and the client. The available scopes are all, serverOnly, none.

info

none is not typically used in serverpod apps. It is intended for the serverpod framework, itself.

class: SelectivelyHiddenClass
fields:
hiddenSecretKey: String, scope=serverOnly
publicKey: String
info

Serverpod's models can easily be saved to or read from the database. You can read more about this in the Database section.

JSON key aliasing

By default, fields are serialized to JSON using their Dart field name as the key. The jsonKey property allows you to specify a different key name for JSON serialization and deserialization, which is useful when integrating with external APIs that use different naming conventions.

class: User
fields:
displayName: String, jsonKey=display_name
emailAddress: String, jsonKey=email
createdAt: DateTime, jsonKey=created_at

This generates a class where the Dart field names remain camelCase, but the JSON representation uses the specified keys:

// Dart field names
var user = User(
displayName: 'John Doe',
emailAddress: 'john@example.com',
createdAt: DateTime.parse('2024-01-15T10:30:00.000Z'),
);

// Serializes to JSON with custom keys
// {
// "display_name": "John Doe",
// "email": "john@example.com",
// "created_at": "2024-01-15T10:30:00.000Z"
// }

This is particularly helpful when:

  • Consuming external APIs that use snake_case or other naming conventions
  • Working with legacy systems that have specific JSON field requirements
  • Integrating with third-party services like MongoDB (e.g., mapping id to _id)
info

The jsonKey property affects JSON serialization and deserialization. It does not affect the database column name. To customize the database column name, use the column property instead. This is an experimental feature; see the Experimental documentation under Column name override for details.

Immutable classes

By default, generated classes in Serverpod are mutable, meaning their fields can be changed after creation. However, you can make a class immutable by setting the immutable property to true. Immutable classes are especially useful when working with state management solutions or when you need value-based equality.

class: ImmutableUser
immutable: true
fields:
name: String
email: String

When you mark a class as immutable:

  1. All fields become final: Fields cannot be reassigned after the object is created
  2. Generates operator ==: Provides deep equality comparison between instances
  3. Generates hashCode: Ensures instances with the same values have the same hash code
  4. Compatible with copyWith: You can still create modified copies of immutable objects using the copyWith method

Example usage:

var user1 = ImmutableUser(name: 'Alice', email: 'alice@example.com');
var user2 = ImmutableUser(name: 'Alice', email: 'alice@example.com');

// Equality comparison works based on values
print(user1 == user2); // true

// Fields are final and cannot be reassigned
// user1.name = 'Bob'; // This would cause a compile error

// Use copyWith to create modified copies
var user3 = user1.copyWith(name: 'Bob');
print(user3.name); // Bob
print(user3.email); // alice@example.com

Exception

The Serverpod models supports creating exceptions that can be thrown in endpoints by using the exception keyword. For more in-depth description on how to work with exceptions see Error handling and exceptions.

exception: MyException
fields:
message: String
errorType: MyEnum

Enum

It is easy to add custom enums with serialization support by using the enum keyword.

enum: Animal
values:
- dog
- cat
- bird

By default the serialization will convert the enum to an int representing the index of the value. Changing the order may therefore have unforeseen consequences when reusing old data (such as from a database). Changing the serialization to be based on the name instead of index is easy.

enum: Animal
serialized: byName
values:
- dog
- cat
- bird

serialized has two valid values byName and byIndex. When using byName the string literal of the enum is used, when using byIndex the index value (0, 1, 2, etc) is used.

info

It's recommended to always set serialized to byName in any new Enum models, as this is less fragile and will be changed to the default setting in version 3 of Serverpod.

Default value

A default value is used when an unknown value is deserialized. This can happen, for example, if a new enum option is added and older clients receive it from the server, or if an enum option is removed but the database still contains the old value.

To configure a default value, use the default keyword.

enum: Animal
serialized: byName
default: unknown
values:
- unknown
- dog
- cat
- bird

In the example above, if the Enum Animal receives an unknown option such as "fish" it will be deserialized to Animal.unknown. This is useful for maintaining backward compatibility when changing the enum values.

warning

If no default value is specified, deserialization of unknown values will throw an exception. Adding a default value prevents these exceptions, but may also hide real issues in your data. Use this feature with caution.

Enhanced enums with properties

Serverpod supports enhanced enums that can have custom properties attached to each value. This is useful when you need to associate additional data with enum values, such as display names, codes, or configuration values.

To define an enhanced enum, add a properties section that declares the property names and types, then specify property values for each enum value:

enum: HttpStatus
serialized: byName
properties:
code: int
message: String
values:
- ok:
code: 200
message: 'OK'
- notFound:
code: 404
message: 'Not Found'
- internalError:
code: 500
message: 'Internal Server Error'

This generates an enhanced Dart enum with the specified properties:

enum HttpStatus implements SerializableModel {
ok(200, 'OK'),
notFound(404, 'Not Found'),
internalError(500, 'Internal Server Error');

const HttpStatus(this.code, this.message);

final int code;
final String message;

// Serialization methods...
}

You can then access the properties on any enum value:

var status = HttpStatus.ok;
print(status.code); // 200
print(status.message); // OK

Supported property types

Enhanced enum properties support the following types:

  • int / int?
  • double / double?
  • bool / bool?
  • String / String?

Default property values

Properties can have default values, making them optional when defining enum values. The syntax follows the same pattern as class field defaults:

enum: Priority
properties:
level: int
description: String, default='No description'
values:
- low:
level: 1
- medium:
level: 2
description: 'Medium priority'
- high:
level: 3
description: 'High priority - handle first'

In this example, low will use the default description 'No description', while medium and high have explicit descriptions.

Adding documentation

Serverpod allows you to add documentation to your serializable objects in a similar way that you would add documentation to your Dart code. Use three hashes (###) to indicate that a comment should be considered documentation.

### Information about a company.
class: Company
fields:
### The name of the company.
name: String

### The date the company was founded, if known.
foundedDate: DateTime?

### A list of people currently employed at the company.
employees: List<Employee>

Generated code

Serverpod generates some convenience methods on the Dart classes.

copyWith

The copyWith method allows for efficient object copying with selective field updates and is available on all generated classes. Here's how it operates:

var john = User(name: 'John Doe', age: 25);
var jane = john.copyWith(name: 'Jane Doe');

The copyWith method generates a deep copy of an object, preserving all original fields unless explicitly modified. It can distinguish between a field set to null and a field left unspecified (undefined). When using copyWith, any field you don't update remains unchanged in the new object.

toJson / fromJson

The toJson and fromJson methods are generated on all models to help with serialization. Serverpod manages all serialization for you out of the box and you will rarely have to use these methods by your self. See the Serialization section for more info.

Custom methods

Sometimes you will want to add custom methods to the generated classes. The easiest way to do this is with Dart's extension feature.

extension MyExtension on MyClass {
bool isCustomMethod() {
return true;
}
}

Default Values

Serverpod supports defining default values for fields in your models. These default values can be specified using three different keywords that determine how and where the defaults are applied:

Keywords

  • default: This keyword sets a default value for both the model (code) and the database (persisted data). It acts as a general fallback if more specific defaults aren't provided.
  • defaultModel: This keyword sets a default value specifically for the model (the code side). If defaultModel is not provided, the model will use the value specified by default if it's available.
  • defaultPersist: This keyword sets a default value specifically for the database. If defaultPersist is not provided, the database will use the value specified by default if it's available.

How priorities work

  • For the model (code side): If both defaultModel and default are provided, the model will use the defaultModel value. If defaultModel is not provided, it will fall back to using the default value.
  • For the database (persisted data): If both defaultPersist and default are provided, the database will use the defaultPersist value. If defaultPersist is not provided, it will fall back to using the default value.

You can use these default values individually or in combination as needed. It is not required to use all default types for a field.

info

When using default or defaultModel in combination with defaultPersist, it's important to understand how the interaction between these keywords affects the final value in the database.

If you set a default or defaultModel value, the model's field or variable will have a value when it's passed to the database—it will not be null. Because of this, the SQL query will not use the defaultPersist value since the field already has a value assigned by the model. In essence, assigning a default or defaultModel is like directly providing a value to the field, and the database will use this provided value instead of its own default.

This means that defaultPersist only comes into play when the model does not provide a value, allowing the database to apply its own default setting.

Supported default values

Boolean

TypeKeywordDescription
Booleantrue or falseSets the field to a boolean value, either true or false.

Example:

boolDefault: bool, default=true

DateTime

TypeKeywordDescription
Current Date and TimenowSets the field to the current date and time.
Specific UTC DateTimeUTC DateTime string in the format yyyy-MM-dd'T'HH:mm:ss.SSS'Z'Sets the field to a specific date and time.

Example:

dateTimeDefaultNow: DateTime, default=now
dateTimeDefaultUtc: DateTime, default=2024-05-01T22:00:00.000Z

Double

TypeKeywordDescription
DoubleAny double valueSets the field to a specific double value.

Example:

doubleDefault: double, default=10.5

Duration

TypeKeywordDescription
Specific DurationA valid duration in the format Xd Xh Xmin Xs XmsSets the field to a specific duration value. For example, 1d 2h 10min 30s 100ms represents 1 day, 2 hours, 10 minutes, 30 seconds, and 100 milliseconds.

Example:

durationDefault: Duration, default=1d 2h 10min 30s 100ms

Enum

TypeKeywordDescription
EnumAny valid enum valueSets the field to a specific enum value.

Example:

enum: ByNameEnum
serialized: byName
values:
- byName1
- byName2
enum: ByIndexEnum
serialized: byIndex
values:
- byIndex1
- byIndex2
class: EnumDefault
table: enum_default
fields:
byNameEnumDefault: ByNameEnum, default=byName1
byIndexEnumDefault: ByIndexEnum, default=byIndex1

In this example:

  • The byNameEnumDefault field will default to 'byName1' in the database.
  • The byIndexEnumDefault field will default to 0 (the index of byIndex1).

Integer

TypeKeywordDescription
IntegerAny integer valueSets the field to a specific integer value.

Example:

intDefault: int, default=10

String

TypeKeywordDescription
StringAny string valueSets the field to a specific string value.

Example:

stringDefault: String, default='This is a string'

UuidValue

TypeKeywordDescription
Random UUIDrandomGenerates a random UUID. On the Dart side, Uuid().v4obj() is used. On the database side, gen_random_uuid() is used.
Random UUIDv7random_v7Generates a random UUIDv7. On the Dart side, Uuid().v7obj() is used. On the database side, a generated gen_random_uuid_v7() function is used.
UUID StringValid UUID in the format 'xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx' where M is the UUID version field. The upper two or three bits of digit N encode the variant. E.g. '550e8400-e29b-41d4-a716-446655440000'Assigns a specific UUID to the field.

Example:

uuidDefaultRandom: UuidValue, default=random
uuidDefaultUuid: UuidValue, default='550e8400-e29b-41d4-a716-446655440000'
uuidDefaultRandomUuidV7: UuidValue, default=random_v7

Example

class: DefaultValue
table: default_value
fields:
### Sets the current date and time as the default value.
dateTimeDefault: DateTime, default=now

### Sets the default value for a boolean field.
boolDefault: bool, defaultModel=false, defaultPersist=true

### Sets the default value for an integer field.
intDefault: int, defaultPersist=20

### Sets the default value for a double field.
doubleDefault: double, default=10.5, defaultPersist=20.5

### Sets the default value for a string field.
stringDefault: String, default="This is a string", defaultModel="This is a string"

Keywords

KeywordNoteclassexceptionenum
valuesA special key for enums with a list of all enum values.
serializedSets the mode enums are serialized in
propertiesDefines custom properties for enhanced enums.
immutableBoolean flag to generate an immutable class with final fields, equals operator, and hashCode.
serverOnlyBoolean flag if code generator only should create the code for the server.
tableA name for the database table, enables generation of database code.
managedMigrationA boolean flag to opt out of the database migration system.
fieldsAll fields in the generated class should be listed here.
type (fields)Denotes the data type for a field.
requiredMakes the field as required. This keyword can only be used for nullable fields.
scopeDenotes the scope for a field.
jsonKeySets a custom key name for JSON serialization and deserialization.
persistA boolean flag if the data should be stored in the database or not can be negated with !persist
relationSets a relation between model files, requires a table name to be set.
nameGive a name to a relation to pair them.
parentSets the parent table on a relation.
fieldA manual specified foreign key field.
onUpdateSet the referential actions when updating data in the database.
onDeleteSet the referential actions when deleting data in the database.
optionalA boolean flag to make a relation optional.
indexesCreate indexes on your fields / columns.
fields (index)List the fields to create the indexes on.
type (index)The type of index to create.
parameters (index)Parameters for specialized index types like HNSW and IVFFLAT vector indexes.
distanceFunction (index)Distance function for vector indexes (l2, innerProduct, cosine, l1).
uniqueBoolean flag to make the entries unique in the database.
defaultSets the default value for both the model and the database. This keyword cannot be used with relation.
defaultModelSets the default value for the model side. This keyword cannot be used with relation.
defaultPersistSets the default value for the database side. This keyword cannot be used with relation and !persist.
extendsSpecifies a parent class to inherit from.
sealedBoolean flag to create a sealed class hierarchy, enabling exhaustive type checking.