mcpField
S
serverpod-models

serverpod-models

github.com/serverpodDevelopment3.3k45 installsUpdated 1d ago
Repository

About serverpod-models

serverpod-models is a skill for developers defining and maintaining models in Serverpod projects. It explains how `.spy.yaml` files generate Dart classes, database tables, and serialization behavior.

Key features

  1. 01Define models, enums, and exceptions
  2. 02Configure field types and defaults
  3. 03Generate tables and ORM relations
  4. 04Set indexes and relation rules
  5. 05Manage serialization compatibility

Use cases

  1. 01Define shared server and client models
  2. 02Configure model persistence in a database
  3. 03Design relations, indexes, and primary keys
  4. 04Maintain client-compatible data structures

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/serverpod/serverpod --skill serverpod-models

After installing, type/serverpod-modelsin Claude Code to confirm it loaded.

Bundled filesreferences/client-side-database.mdreferences/custom-serialization.md

Details

From the project README

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.

Basic class

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

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

Database table

Add table for a database table + ORM:

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).
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:

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

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.

exception: MyException
fields:
  message: String
  errorType: MyEnum

Throw on server, catch on client:

// 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

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:

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

Comments

Markdown supported