Schema Definition with Decorators
The @type() decorator style is the classic way of defining schema structures, and remains fully supported. The schema builder (schema() + t.*) is the recommended style for new code, but both produce the identical wire format and interoperate freely: t.array(DecoratorClass) and @type(BuilderClass) both work.
Schema examples across the docs offer a Decorators tab, and your tab selection is remembered across pages. This page collects the decorator-specific setup and details in one place.
TypeScript Config
The decorator style requires two settings in your tsconfig.json. Enable experimentalDecorators and disable useDefineForClassFields:
{
"compilerOptions": {
"experimentalDecorators": true,
"useDefineForClassFields": false
}
}These flags are needed only for decorators. The schema() builder works without any compiler configuration. (See #510 for the discussion behind useDefineForClassFields.)
What is @type()?
@type() uses an upcoming JavaScript feature that is yet to be formally established by TC39. type is actually just a function imported from the @colyseus/schema module. By calling type with the @ prefix at the property level means we’re calling it as a property decorator. See the decorators proposal here.
Defining fields
Extend the Schema class and decorate each synchronizable field with @type(), passing one of the primitive type strings:
import { Schema, type } from "@colyseus/schema";
export class MyState extends Schema {
@type("string") currentTurn: string;
}Child schemas
Pass the class itself to @type() to nest one schema inside another:
import { Schema, type } from "@colyseus/schema";
class World extends Schema {
@type("number") width: number;
@type("number") height: number;
@type("number") items: number = 10;
}
class MyState extends Schema {
@type(World) world: World = new World();
}Collections
Collection fields take the child type wrapped in the collection shape: [...] for arrays, { map: ... }, { set: ... }, or { collection: ... }. Initialize each field with its collection instance:
import { Schema, ArraySchema, MapSchema, SetSchema, CollectionSchema, type } from "@colyseus/schema";
class Block extends Schema {
@type("number") x: number;
@type("number") y: number;
}
class Player extends Schema {
@type([ "string" ]) animals = new ArraySchema<string>();
@type([ Block ]) blocks = new ArraySchema<Block>();
@type({ map: Block }) blocksByName = new MapSchema<Block>();
@type({ set: Block }) effects = new SetSchema<Block>();
@type({ collection: Block }) items = new CollectionSchema<Block>();
}The collection instances themselves work exactly as documented in the main reference. Only the field declaration differs between the two styles.
Default values
Use regular class property initializers:
import { Schema, MapSchema, type } from "@colyseus/schema";
class MyState extends Schema {
@type("number") countdown: number = 60;
@type({ map: Player }) players = new MapSchema<Player>();
}Inheritance
Subclasses may define their own serialized fields, and collections accept inherited types of the same base:
import { Schema, MapSchema, type, entity } from "@colyseus/schema";
class Item extends Schema {/* base Item fields */}
class Weapon extends Item {
@type("number") damage: number;
}
// A subclass that adds NO new @type() fields must be registered
// explicitly with @entity: field decorators are what register a
// class with the serializer, and this class has none of its own.
@entity
class Shield extends Item {}
class Inventory extends Schema {
@type({ map: Item }) items = new MapSchema<Item>();
}
const inventory = new Inventory();
inventory.items.set("left", new Weapon());
inventory.items.set("right", new Shield());View tags
Fields visible only to selected clients take the @view() decorator alongside @type():
import { Schema, type, view } from "@colyseus/schema";
class Player extends Schema {
// visible to all
@type("string") name: string;
// any `view.add(player)` will see this field
@view() @type("number") health: number;
// only `view.add(player, 1)` will see this field
@view(1) @type("number") position: number;
}See State View for how StateView works. Everything there applies equally to both definition styles.
Delivery modifiers
@unreliable, @patchOnly, and @fullStateOnly control when and how a field reaches clients. Apply them alongside @type():
import { Schema, type, unreliable, patchOnly, fullStateOnly } from "@colyseus/schema";
class Player extends Schema {
// tick patches ride the unreliable channel (WebTransport only)
@unreliable @type("number") aimYaw: number;
// never in a full state sync: invisible to late joiners
@patchOnly @type("number") flashIntensity: number;
// full state sync only, never a tick patch
@fullStateOnly @type("uint16") spawnIndex: number;
}@patchOnly and @fullStateOnly are mutually exclusive: combining them throws at decoration time. Streaming collections use the type descriptor instead: @type({ stream: Entity }), with an optional priority key.
The semantics, use cases and cautions are documented in the main reference → Delivery modifiers. That includes the transport requirement for @unreliable, which is experimental and reaches clients only over WebTransport.
Deprecating fields
Flag a retired field with @deprecated() while keeping its slot in the encoding order:
import { Schema, type, deprecated } from "@colyseus/schema";
class MyState extends Schema {
@deprecated() @type("string") myField: string;
// new fields always at the end of the structure
@type("string") newField: string;
}See Versioning for the full backwards/forwards compatibility workflow.
Legacy: defineTypes()
defineTypes() is deprecated: it still works, but logs a deprecation warning at runtime and in schema-codegen. Migrate to schema() with t.* builders. (It was briefly removed in early schema 5 releases, 5.0.0–5.0.8. Update @colyseus/schema if you hit a missing-export error.)
import { Schema, defineTypes } from "@colyseus/schema";
class MyState extends Schema {
}
defineTypes(MyState, {
currentTurn: "string",
});defineTypes() delegates to the same pipeline as the @type() decorator, so unlike schema() it still accepts raw type strings ("string", "number") as field values.
See the 0.18 migration guide for upgrade steps.