Skip to main content

serverpod-models

Define Serverpod data models in YAML (.spy.yaml) — fields, defaults, database tables, relations, enums, exceptions. Use when creating or editing .spy.yaml files. To query the generated ORM instead, use serverpod-database.

Source facts

Repository
serverpod/wonky
Last source activity
October 25, 2026 at 12:09
Detected SKILL.md language
English
Stars
6
Forks
5

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
3 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
serverpod-models
description
Define Serverpod data models in YAML (.spy.yaml) — fields, defaults, database tables, relations, enums, exceptions. Use when creating or editing .spy.yaml files. To query the generated ORM instead, use serverpod-database.
user-invocable
false
# Serverpod Models Models are defined in `.spy.yaml` files anywhere under server `lib/`. They generate Dart classes for server and client, and optionally database tables. After each change to models, ensure that the code is generated (automatically when the Serverpod runner is running or manually with `serverpod generate`). If models with `table` have changed, the database schema must be updated following the [migration workflow](../serverpod-migrations/SKILL.md). ## Basic class ```yaml class: Company fields: name: String foundedDate: DateTime? employees: List<Employee> ``` Field types: `bool`, `int`, `double`, `String`, `Duration`, `DateTime`, `ByteData`, `UuidValue`, `Uri`, `BigInt`, generated classes/enums/exceptions, `List<T>`, `Map<K,V>`, `Set<T>`, `Record`. Use `?` for nullable. ## Required fields ```yaml class: Person fields: name: String nickname: String?, required ``` ## Database table Add `table` for a database table + ORM: ```yaml class: Company table: company fields: name: String foundedDate: DateTime? ``` ## Default values - `default=` sets the value on both the Dart model and the database column. - `defaultModel=` sets it only on the Dart model (used when the object is created in Dart). - `defaultPersist=` sets it only on the database column (requires the field to be nullable in Dart, unless it is the `id`). ```yaml class: Post table: post fields: title: String, default='Untitled' createdAt: DateTime, default=now publishedAt: DateTime?, defaultPersist=now externalId: UuidValue, default=random_v7 views: int, default=0 isPublic: bool, default=false ``` `now` (DateTime), `random`/`random_v7` (UuidValue), and quoted literals are supported. A field with a default does not need to be passed to the constructor. ## Custom id types The `id` field is `int` with a `serial` default unless declared explicitly. Declare it to opt into UUID primary keys: ```yaml class: Company table: company fields: id: UuidValue?, defaultPersist=random_v7 # database generates the id name: String ``` - `defaultPersist=random_v7` — the database generates the id on insert (`random` for v4). - `defaultModel=random_v7` — Dart generates the id when the object is created, so it is known before insert. - `id: int?, defaultPersist=serial` — the explicit form of the default behavior. Relations to a model with a custom id type use the same type on the foreign key field. ## Scope - **Server-only class:** `serverOnly: true` - **Per-field:** `scope=serverOnly`, `scope=none` (default `all`) - **Non-persisted field:** `!persist` (not stored in DB) - **JSON key alias:** `jsonKey=display_name` ## Immutable classes `immutable: true` — final fields, `==`, `hashCode`, `copyWith`. ## Inheritance - `extends: ParentClass` — child inherits parent fields. Only one class in hierarchy has `table`. - `sealed: true` — abstract sealed hierarchy for exhaustive subtypes. No `table` on sealed class. If parent is `serverOnly`, children must be too. Children cannot redefine parent fields. ## Enums ```yaml enum: Status values: - pending - active - completed ``` Default serialization: `byName`. Set `serialized: byIndex` to use index. ## Exceptions Use `exception:` instead of `class:` for serializable exceptions. Same field types as classes; supports `default` and `defaultModel`. Uncaught exceptions become generic 500 errors on the client; only serializable exceptions send their data. ```yaml exception: MyException fields: message: String errorType: MyEnum ``` Throw on server, catch on client: ```dart // Server throw MyException(message: 'Failed', errorType: MyEnum.thingyError); // Client try { await client.example.doThingy(); } on MyException catch (e) { print(e.message); } ``` Serializable exceptions can also be sent over streams (both directions; the stream closes after). Do not put sensitive data in exception fields — they are sent to the client. ## Indexes ```yaml indexes: company_name_idx: fields: name unique: true nulls_distinct: false # PostgreSQL only, treats NULLs as equal ``` Field-level `unique` auto-generates a btree unique index: ```yaml fields: tenantId: int category: String # single-column unique index email: String, unique # composite unique index on (category, value) value: String, unique(per=category) # composite unique index on (category, tenantId, value) amount: int, unique(per=[category, tenantId]) ``` ## Relations **One-to-one** (FK with unique index): - ID field: `addressId: int, relation(parent=address)` + unique index on `addressId` - Object field: `address: Address?, relation` (generates `addressId`) - Optional: `relation(optional)` for nullable FK; `relation(field=customId)` for custom FK name - Bidirectional: same `relation(name=...)` on both sides; `fk` on the FK side (or `field=` for a custom FK name) **One-to-many:** - "One" side: `employees: List<Employee>?, relation` - "Many" side: `companyId: int, relation(parent=company)` (no unique index) - Bidirectional: `relation(name=company_employees)` on both sides **Many-to-many:** Use a join table model with two relation fields. **Referential actions:** `relation(onDelete=Cascade)` and `relation(onUpdate=...)` map to the SQL foreign key actions (`Cascade`, `SetNull`, `SetDefault`, `Restrict`, `NoAction`). `SetNull` requires a nullable foreign key (a nullable object relation field, or `relation(optional)`). Add `deferrable` to allow constraint checks to be deferred explicitly, or `deferred` to defer them by default until commit. `Restrict` actions are always checked immediately, even on a deferred constraint; use `NoAction` when the check must be deferrable. Querying: `include` for eager loading, `includeList` with `where`/`orderBy`/`limit`/`offset` for list relations. `attach`/`detach` for managing relations. ## Backward compatibility To keep backward compatibility, do not change or remove fields in serialized classes used by clients. Add new fields only if nullable or with a default value, so older clients that don't send the field still work. ## Reference files - [`references/client-side-database.md`](references/client-side-database.md) — the `database:` keyword, generating tables on the client. - [`references/custom-serialization.md`](references/custom-serialization.md) — using hand-written or Freezed classes as model types via `extraClasses`.
View on GitHub