Schemalang reference

This page is a reference to schemalang, used to write schema files.

For information about how to design what goes into components, see Designing components.

Directory structure and files

The schema or a SpatialOS world is stored in the schema subdirectory of a project, using 'schema files' (files with the .schema extension).

Package definition

Each schema file must have a package definition at the top (eg package;).

You can arrange the files in arbitrary directory structures in the schema directory. For example, you could put the code for the package in the directory schema/foo/bar.schema.


Schemalang supports both // line comments and /* */ block comments.

Character encoding

Schema files are encoded in UTF-8, although characters outside of 7-bit ASCII are only valid inside comments.

Newlines can be encoded as either LF (Unix-style) or CR+LF (Windows-style).

See Annotations for how to represent special characters inside string literals.


Define components using the component keyword.

Components contain:

  • (required) an explicit component ID
  • (optional) properties (with a type, a name, and an explicit property ID)
  • (optional) events
  • (optional) commands

An example component (in schemalang):

package improbable.example;

component Health {
  id = 1234;
  uint32 current_health = 1; // a property
  uint32 max_health = 2; // another property


Component IDs must be unique across the entire schema (including library dependencies with their own schemas). Property IDs must be unique within the component.

These two types of IDs are essential for backward compatibility: they let you change the schema without breaking existing snapshots.

Component IDs below 100 and from 19000 to 19999 are reserved for Improbable use.

The maximum allowed component ID is 536870911 (29 bits).

Component events

Define an event using the event T name; syntax. For example (in schemalang):

package improbable.example;

type SwitchToggled {
  int64 time = 1;

component Switch {
  id = 1234;
  bool is_enabled = 1;
  event SwitchToggled toggled;

The above example allows the component to trigger a toggled event that contains a time field. Other workers can react to this event.

Component commands

Define a command using the command keyword as follows (in schemalang):

package improbable.example;

type DamageRequest {
  uint32 amount = 1;

type DamageResponse {}

component Health {
  id = 1234;
  uint32 health = 1;
  command DamageResponse damage(DamageRequest);

Commands require both a request and a response type. The request type is the data that is sent to the worker with write access authority to the component. The response type is sent back to the worker that issued the command.


Primitive types

The primitive types available are:

Syntax Type Notes
bool Boolean True or false.
uint32, uint64 Unsigned integer Variable-length encoding; smaller values use fewer bits.
int32, int64 Signed integer Variable-length encoding; smaller values use fewer bits. Negative values are represented in the usual two's-complement manner, and so use the maximum number of bits.
sint32, sint64 Zig-zag signed integer Variable-length zig-zag encoding; smaller absolute values use fewer bits. More space-efficient than int32 or int64 when values are likely to be negative.
fixed32, fixed64, sfixed32, sfixed64 Fixed-width integer Fixed-width encoding (always 4 or 8 bytes depending on type); more space-efficient when values are likely to be very large.
float, double Floating-point
string, bytes String of characters or bytes Strings should always be either ASCII or UTF-8.
EntityId ID of an entity A special version of int64 used to store the ID of a SpatialOS entity. IDs are > 0.
Entity A component set A data structure that represents an arbitrary collection of components.

User-defined types

You can define and reuse custom types, using the type keyword. Custom types consist of field definitions which look exactly the same as in components.

You can also define types within the scope of an outer type, to make the types similarly nested in generated code. Refer to nested types from elsewhere by writing out the path (perhaps relative to the current scope) with dots as separators.

Here is an example (in schemalang):

package improbable.example;

type Vector3f {
  float x = 1;
  float y = 2;
  float z = 3;

type Vector3d {
  double x = 1;
  double y = 2;
  double z = 3;

type Movement {
  Vector3d target_position = 1;
  Vector3d target_direction = 2;

type Foo {
  type Nested {
    int32 nested_int = 1;

  int32 foo_int = 1;
  double foo_double = 2;

type Bar {
  type Nested {}

  Foo foo = 1;
  // Resolves to Bar.Nested
  Nested bar_nested = 2;
  // Resolves to Foo.Nested
  Foo.Nested foo_nested = 3;

Again, field IDs must be unique within the type.


You can define enumerations and use them like built-in types. Like types, enums can be defined within the scope of an outer type.

Example (in schemalang):

package improbable.example;

enum Color {
  RED = 0;
  GREEN = 1;
  BLUE = 2;

type ColorData {
  // enum Color could be defined inside this scope, too!
  Color color = 1;

Collection types

The collection types available are:

  • An option<T> represents either no value (empty) or a single T value.
  • A list<T> represents zero or more T values.
  • A map<K, V> represents a map from keys of type K to values of type V.

Collection type fields can be transient.

Example (in schemalang):

package improbable.example;

type SomeDataType {
  option<int32> an_integer = 1;
  list<SomeDataType> more_data = 2;
  map<EntityId, EntityId> entity_to_entity_map = 3;

You can't create nested collections (like lists of lists, maps of lists, lists of options, and so on)
directly. Use wrapper types instead:

package improbable.example;

type Data {
  type InnerList {
    list<int32> value = 1;
  list<InnerList> list_of_list = 1;

Importing types

Schema files can use types defined in other schema files by importing the files. For example, if a file called foo/bar.schema contains the following (in schemalang):


type Baz {}

Another schema file can use the type Baz like this:

package improbable.example;
import "foo/bar.schema";

component BazComponent {
  id = 1234; baz = 1;

The path of the file is relative to the schema directory (either the schema directory of the SpatialOS project, or the schema directory of some other library dependency).

To avoid ambiguity when working with packages sharing similar names, you can prefix references to user-defined types with a dot, to indicate a fully-qualified name (including package). For example, to refer to Baz above, you could write


Names of properties, events and commands must be in lowercase_with_underscores. Names of types (components, user-defined types and enumerations) must be in UpperCamelCase.

This is to allow generated code to compile correctly in all target languages, no matter what names are used. The naming rules are designed to avoid naming conflicts in the generated code.
For example, if there could be two properties named foo_bar and fooBar, it's not obvious how to name the accessors for these properties in (for example) Java while conforming to Java's conventions.
Enforcing a consistent naming style avoids this problem, reduces confusion, and makes the schema definition clearer.

Some names are reserved by the schema compiler and cannot be used. For example, for each component ComponentName, the compiler will generate a type called ComponentNameData. Trying to create a user-defined type with this name will result in an error unless you manually specify the data type for the component as described below.

Advanced components

Reusable data types

To allow multiple components to share a data type, instead of defining a property inline,
components can reference an external user-defined type for a property. Use the syntax data T; as follows (in schemalang):

package improbable.example;

type SomeData {
  int32 value = 1;

component SomeComponent {
  id = 1234;
  data SomeData;

component AnotherComponent {
  id = 1235;
  data SomeData;

You can't mix this syntax with in-line property definition, or combine multiple data types this way. For example, the following definitions (in schemalang) are not valid and won't compile:

package improbable.example;

type SomeData {
  int32 value = 1;

component ThisComponentWontCompile {
  id = 1234;
  data SomeData;
  int32 extra_property = 2;

type OtherData {
  int32 value = 3;

component ThisComponentAlsoWontCompile {
  id = 1337;
  data SomeData;
  data OtherData;

Note: The above example schemalang is not valid and won't compile.

Transient fields

Transient fields are deprecated and we'll remove them in a future version of the Worker SDK.

Collection type fields can be marked transient. The data in these fields won't be saved in snapshots taken from a deployment, or loaded from a snapshot at the start of a deployment.

This makes clearing per-deployment state in snapshots easier. For example, a list of unprocessed player moves might be needed in a deployment but it is unlikely to be something that needs to be persisted across deployments. Similarly, you may want per-deployment player abilities and a per-deployment score. By marking the fields transient, as below, you can take a snapshot and start a deployment from it without having to manually clear the fields.

The example below is in schemalang.

package improbable.example;

component PlayerState {
  id = 1234;
  transient list<Move> moves_to_process = 1;
  transient map<AbilityName, Ability> active_abilities = 2;
  Score score = 3;

type Score {
  transient option<int32> deployment_score = 1;
  int32 alltime_score = 2;

For entities that need to be persisted across deployments, use the Persistence component.


Schemalang supports annotating schema definitions with an instance of any user-defined schema type. For example, you can annotate enums, events or commands. These annotations do not affect generated code for the Worker SDK; however, they are available in the JSON representation of schema, for use in custom code generation. The following shows all the schema definitions that you can annotate.

The example below is in schemalang.

package improbable.example;
type SomeAnnotation{}
// Note: You can use either SomeAnnotation or SomeAnnotation() for an empty schema type.
[SomeAnnotation]  // Annotating the enum definition 'Color'.
enum Color {
  [SomeAnnotation()]  // Annotating the enum value definition 'RED'.
  RED = 0;
[SomeAnnotation]  // Annotating the schema type 'SomeDataType'.
type SomeDataType {
  // Note: You can use a type for annotation regardless of where the type is declared.
  [Nested(1)]  // Annotating the schema type 'Nested' with itself.
  type Nested {int32 a = 1;}
  [SomeAnnotation]  // Annotating the field 'some_field'.
  option<int32> some_field = 1;
[SomeAnnotation]  // Annotating the component 'SomeComponent'.
component SomeComponent {
  id = 120; // Note: You can't annotate component IDs.
  [SomeAnnotation]  // Annotating the field 'some_field'.
  SomeDataType some_field = 1;
  [SomeAnnotation]  // Annotating the command 'some_command'.
  command SomeDataType some_command(SomeDataType);
  [SomeAnnotation]  // Annotating the event 'some_event'.
  event SomeDataType some_event;
[SomeAnnotation]  // Annotating the component 'SomeOtherComponent'.
component SomeOtherComponent {
  id = 121;
  data SomeDataType; // Note: You can't annotate the keyword 'data'.

Note: Entity fields cannot be instantiated in annotations.


The example above showed a user-defined type without fields being used as an annotation. However, you can also use a user-defined type with fields.

  • Each field must be instantiated with a value of its corresponding type.
  • Fields can be named or unnamed, for example SomeType(field_a = 1) or SomeType(1).
  • An instance of a type cannot have a mix of named and unnamed fields.
  • In the case that fields are unnamed, they are expected to be given in the order declared in the
    schema type definition.
  • Note that field IDs do not affect the expected ordering.

The following is an example of more complex schema types:

The example below is in schemalang.

enum SomeEnum {
  FOO = 1;
type ComplexType {
  type Nested {int32 a = 1;}
  bool bool_value = 1;
  int32 int_value = 2;
  float float_value = 3;
  string string_value = 4;
  bytes bytes_value = 5;
  EntityId id_value = 6;
  Nested type_value = 7;
  SomeEnum enum_value = 8;
type Collections {
  option<int32> option_value = 1;
  list<int32> list_value = 2;
  map<string, int32> map_value = 3;
// Note: Type names are interpreted according to the current scope,
//       though you can also use fully-qualified names.
[ComplexType(true, 32, 5.0, "foo", "bar", 5, ComplexType.Nested(50), SomeEnum.FOO)]
type AnnotatedType1 {}
// Note: '_' is used for an empty option.
[Collections(option_value = _, list_value = [], map_value = {})]
type AnnotatedType2 {}
[Collections(option_value = 1, list_value = [1, 2], map_value = {"foo":1, "bar":2})]
type AnnotatedType3 {}


The value of string and bytes fields are both specified using the string literal syntax, although the semantics are slightly different. A string value is a sequence of (Unicode) characters, meaning that a character encoding needs to be specified for each final output format they can appear in, while a bytes value is a merely sequence of bytes, and is never transformed.

In either case, only 7-bit ASCII characters are permitted to occur directly inside string literals. Other characters or byte values can be represented with the following escape sequences:

Syntax Meaning in string Meaning in bytes
\\ Backslash (U+005c) 0x5c
\" Double quote (U+0022) 0x22
\0 Null (U+0000) 0x00
\a Bell (U+0007) 0x07
\b Backspace (U+0008) 0x08
\t Tab (U+0009) 0x09
\n Line feed (U+000a) 0x0a
\v Vertical tab (U+000b) 0x0b
\f Form feed (U+000c) 0x0c
\r Carriage return (U+000d) 0x0d
\xXX U+00XX 0xXX
\uXXXX U+XXXX Not permitted

The X characters in the above table stand for arbitrary hexadecimal digits (upper or lower case).

Syntax highlighting plugins

There are several community projects for schema syntax highlighting:

Updated about a year ago

Schemalang reference

Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.