Skip to content

Define a type

By the end of this page the plugin has a Stashed link type, and the inspector lists its fields.

A plugin’s data is ECHO objects, and a type describes them. The app reads the type’s annotations to name an object, pick its icon, and decide whether a person can create one. Defining types and the Type guide cover types in full; this page uses only what PLEX needs.

  1. Declare the type. Type.makeObject gives the schema a DXN and makes it an ECHO type. The annotations tell the app which field is the object’s label, which icon to show, and that the type is one a person creates (UserType). The fields are optional because a new object starts empty.

    src/types/StashedLink.ts
    import * as
    import Schema
    Schema
    from 'effect/Schema';
    import {
    import Annotation
    Annotation
    ,
    import DXN
    DXN
    ,
    import Obj
    Obj
    ,
    import Type
    Type
    } from '@dxos/echo';
    export class
    class StashedLink
    StashedLink
    extends
    import Type
    Type
    .
    const makeObject: <StashedLink>(dxn: DXN.DXN, options?: {
    id?: EntityId;
    }) => Type.MakeObject<StashedLink>

    Factory function to create an ECHO object type.

    Returns a Type.Type entity — a live, in-memory TypeSchema instance, NOT a Schema.Schema. Use Type.InstanceType<typeof Foo> for the instance type and Type.getSchema(Foo) to obtain the underlying Effect Schema.

    The entity's id defaults to EntityId.deterministic(typename, version) so constructing a type never reaches crypto.getRandomValues() — required for Cloudflare workerd, which forbids RNG calls in global (module-evaluation) scope. Pass { id } to override (e.g. with EntityId.random() from a request handler).

    Giving the type a nominal class name keeps its inferred type portable for declaration emit: structural expansion (which can pull in unnameable union members from referenced schemas) is replaced by a reference to the class.

    The second call takes either a schema, or struct fields (wrapped in Schema.Struct) with an optional function that pipes annotations onto the struct.

    @example

    export class Person extends Type.makeObject<Person>(DXN.make('com.example.type.myType', '0.1.0'))(Schema.Struct({
    name: Schema.String,
    })) {}
    export class Tag extends Type.makeObject<Tag>(DXN.make('com.example.type.tag', '0.1.0'))(
    { label: Schema.String },
    Annotation.LabelAnnotation.set(['label']),
    ) {}

    @performance ― O(schema size) once at definition time; builds the type entity eagerly, so call it at module scope, not per use.

    makeObject
    <
    class StashedLink
    StashedLink
    >(
    import DXN
    DXN
    .
    const make: <"com.example.type.stashedLink">(nsid: "com.example.type.stashedLink", version?: string) => DXN.DXN

    Constructs a DXN from an NSID (and optional version). Throws if the result is not a valid DXN. Use tryMake for non-throwing string parsing.

    Static NSID strings are validated at compile time via

    Name

    : the final segment must be camelCase (no hyphens). Template-literal strings with a runtime prefix are accepted here but still validated at runtime.

    @example make('org.dxos.type.calendar') → 'dxn:org.dxos.type.calendar'

    @example make('org.dxos.type.calendar', '1.0.0') → 'dxn:org.dxos.type.calendar:1.0.0'

    make
    ('com.example.type.stashedLink', '0.1.0'),
    )(
    import Schema
    Schema
    .
    function Struct<{
    readonly title: Schema.optional<Schema.String>;
    readonly url: Schema.optional<Schema.String>;
    readonly note: Schema.optional<Schema.String>;
    readonly read: Schema.optional<Schema.Boolean>;
    }>(fields: {
    readonly title: Schema.optional<Schema.String>;
    readonly url: Schema.optional<Schema.String>;
    readonly note: Schema.optional<Schema.String>;
    readonly read: Schema.optional<Schema.Boolean>;
    }): Schema.Struct<{
    readonly title: Schema.optional<Schema.String>;
    readonly url: Schema.optional<Schema.String>;
    readonly note: Schema.optional<Schema.String>;
    readonly read: Schema.optional<Schema.Boolean>;
    }>

    Defines a struct schema from a map of field schemas.

    Details

    Each field value is a schema. Use

    optionalKey

    or

    optional

    to mark fields as optional, and

    mutableKey

    to mark them as mutable.

    The resulting schema's Type is a readonly object type with the fields' decoded types. The Encoded form mirrors the field schemas' encoded types. Declared fields may be inherited and are copied to own properties in the output. The __proto__ field is accepted only when it is an own property. Parsing does not guarantee that output keys retain their input order.

    Example (Defining a basic struct)

    import { Schema } from "effect"
    const Person = Schema.Struct({
    name: Schema.String,
    age: Schema.Number,
    email: Schema.optionalKey(Schema.String)
    })
    // { readonly name: string; readonly age: number; readonly email?: string }
    type Person = typeof Person.Type
    Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 }) // => { name: "Alice", age: 30 }

    @stability ― stable

    @category ― constructors

    @since ― 3.10.0

    Struct
    ({
    title: Schema.optional<Schema.String>
    title
    :
    import Schema
    Schema
    .
    const optional: optionalLambda
    <Schema.String>(self: Schema.String) => Schema.optional<Schema.String>

    Type-level representation returned by

    optional

    .

    Marks a struct field as optional, allowing the key to be absent or undefined.

    Details

    The resulting property may be absent or explicitly set to undefined. Equivalent to optionalKey(UndefinedOr(S)).

    Use

    optionalKey

    instead if you want exact optional semantics (absent only, not undefined).

    Example (Defining an optional field accepting undefined)

    import { Schema } from "effect"
    const schema = Schema.Struct({
    name: Schema.String,
    age: Schema.optional(Schema.Number)
    })
    // { readonly name: string; readonly age?: number | undefined }
    type Person = typeof schema.Type

    @stability ― stable

    @category ― models

    @since ― 3.10.0

    @stability ― stable

    @category ― combinators

    @since ― 3.10.0

    optional
    (
    import Schema
    Schema
    .
    const String: Schema.String

    Type-level representation of

    String

    .

    Schema for string values. Validates that the input is typeof "string".

    @stability ― stable

    @category ― models

    @since ― 4.0.0

    @stability ― stable

    @category ― schemas

    @since ― 4.0.0

    String
    .
    BottomWithoutNew<string, string, never, never, String, String, string, string, readonly [], string, "readonly", "required", "no-default", "readonly", "required">.annotate(annotations: Schema.Annotations.Bottom<string, readonly []>): Schema.String
    annotate
    ({
    Annotations.Augment.title?: string | undefined
    title
    : 'Title' })),
    url: Schema.optional<Schema.String>
    url
    :
    import Schema
    Schema
    .
    const optional: optionalLambda
    <Schema.String>(self: Schema.String) => Schema.optional<Schema.String>

    Type-level representation returned by

    optional

    .

    Marks a struct field as optional, allowing the key to be absent or undefined.

    Details

    The resulting property may be absent or explicitly set to undefined. Equivalent to optionalKey(UndefinedOr(S)).

    Use

    optionalKey

    instead if you want exact optional semantics (absent only, not undefined).

    Example (Defining an optional field accepting undefined)

    import { Schema } from "effect"
    const schema = Schema.Struct({
    name: Schema.String,
    age: Schema.optional(Schema.Number)
    })
    // { readonly name: string; readonly age?: number | undefined }
    type Person = typeof schema.Type

    @stability ― stable

    @category ― models

    @since ― 3.10.0

    @stability ― stable

    @category ― combinators

    @since ― 3.10.0

    optional
    (
    import Schema
    Schema
    .
    const String: Schema.String

    Type-level representation of

    String

    .

    Schema for string values. Validates that the input is typeof "string".

    @stability ― stable

    @category ― models

    @since ― 4.0.0

    @stability ― stable

    @category ― schemas

    @since ― 4.0.0

    String
    .
    BottomWithoutNew<string, string, never, never, String, String, string, string, readonly [], string, "readonly", "required", "no-default", "readonly", "required">.annotate(annotations: Schema.Annotations.Bottom<string, readonly []>): Schema.String
    annotate
    ({
    Annotations.Augment.title?: string | undefined
    title
    : 'URL' })),
    note: Schema.optional<Schema.String>
    note
    :
    import Schema
    Schema
    .
    const optional: optionalLambda
    <Schema.String>(self: Schema.String) => Schema.optional<Schema.String>

    Type-level representation returned by

    optional

    .

    Marks a struct field as optional, allowing the key to be absent or undefined.

    Details

    The resulting property may be absent or explicitly set to undefined. Equivalent to optionalKey(UndefinedOr(S)).

    Use

    optionalKey

    instead if you want exact optional semantics (absent only, not undefined).

    Example (Defining an optional field accepting undefined)

    import { Schema } from "effect"
    const schema = Schema.Struct({
    name: Schema.String,
    age: Schema.optional(Schema.Number)
    })
    // { readonly name: string; readonly age?: number | undefined }
    type Person = typeof schema.Type

    @stability ― stable

    @category ― models

    @since ― 3.10.0

    @stability ― stable

    @category ― combinators

    @since ― 3.10.0

    optional
    (
    import Schema
    Schema
    .
    const String: Schema.String

    Type-level representation of

    String

    .

    Schema for string values. Validates that the input is typeof "string".

    @stability ― stable

    @category ― models

    @since ― 4.0.0

    @stability ― stable

    @category ― schemas

    @since ― 4.0.0

    String
    .
    BottomWithoutNew<string, string, never, never, String, String, string, string, readonly [], string, "readonly", "required", "no-default", "readonly", "required">.annotate(annotations: Schema.Annotations.Bottom<string, readonly []>): Schema.String
    annotate
    ({
    Annotations.Augment.title?: string | undefined
    title
    : 'Note' })),
    read: Schema.optional<Schema.Boolean>
    read
    :
    import Schema
    Schema
    .
    const optional: optionalLambda
    <Schema.Boolean>(self: Schema.Boolean) => Schema.optional<Schema.Boolean>

    Type-level representation returned by

    optional

    .

    Marks a struct field as optional, allowing the key to be absent or undefined.

    Details

    The resulting property may be absent or explicitly set to undefined. Equivalent to optionalKey(UndefinedOr(S)).

    Use

    optionalKey

    instead if you want exact optional semantics (absent only, not undefined).

    Example (Defining an optional field accepting undefined)

    import { Schema } from "effect"
    const schema = Schema.Struct({
    name: Schema.String,
    age: Schema.optional(Schema.Number)
    })
    // { readonly name: string; readonly age?: number | undefined }
    type Person = typeof schema.Type

    @stability ― stable

    @category ― models

    @since ― 3.10.0

    @stability ― stable

    @category ― combinators

    @since ― 3.10.0

    optional
    (
    import Schema
    Schema
    .
    const Boolean: Schema.Boolean

    Type-level representation of

    Boolean

    .

    Schema for boolean values. Validates that the input is typeof "boolean".

    When to use

    Use to validate values that are already JavaScript booleans.

    @stability ― stable

    @category ― models

    @since ― 4.0.0

    @see ― BooleanFromBit for a schema that decodes bit literals 0 or 1 into a boolean

    @stability ― stable

    @category ― schemas

    @since ― 4.0.0

    Boolean
    .
    BottomWithoutNew<boolean, boolean, never, never, Boolean, Boolean, boolean, boolean, readonly [], boolean, "readonly", "required", "no-default", "readonly", "required">.annotate(annotations: Schema.Annotations.Bottom<boolean, readonly []>): Schema.Boolean
    annotate
    ({
    Annotations.Augment.title?: string | undefined
    title
    : 'Read' })),
    }).
    Pipeable.pipe<Schema.Struct<{
    readonly title: Schema.optional<Schema.String>;
    readonly url: Schema.optional<Schema.String>;
    readonly note: Schema.optional<Schema.String>;
    readonly read: Schema.optional<Schema.Boolean>;
    }>, Schema.Struct<{
    readonly title: Schema.optional<Schema.String>;
    readonly url: Schema.optional<Schema.String>;
    readonly note: Schema.optional<Schema.String>;
    readonly read: Schema.optional<Schema.Boolean>;
    }>, Schema.Struct<...>, Schema.Struct<...>>(this: Schema.Struct<...>, ab: (_: Schema.Struct<...>) => Schema.Struct<...>, bc: (_: Schema.Struct<...>) => Schema.Struct<...>, cd: (_: Schema.Struct<...>) => Schema.Struct<...>): Schema.Struct<...> (+21 overloads)
    pipe
    (
    import Annotation
    Annotation
    .
    const LabelAnnotation: AnnotationHelper<string[]>
    export LabelAnnotation
    LabelAnnotation
    .
    AnnotationHelper<string[]>.set: (value: string[]) => <S extends Schema.Top>(schema: S) => S

    Set the annotation on an Effect schema.

    Only accepts Schema.Top — annotations must be applied to the source schema BEFORE wrapping it with Type.makeObject / Type.makeRelation. In a pipe, place every Annotation.X.set(...) before the Type.make... step.

    set
    (['title']),
    import Annotation
    Annotation
    .
    const IconAnnotation: Annotation.Annotation<Annotation.IconAnnotation>
    export IconAnnotation

    Icon to render in the UI.

    IconAnnotation
    .
    Annotation<IconAnnotation>.set: (value: Annotation.IconAnnotation) => <S extends Schema.Top>(schema: S) => S

    Set the annotation on an Effect schema.

    Only accepts Schema.Top — annotations must be applied to the source schema BEFORE wrapping it with Type.makeObject / Type.makeRelation. In a pipe, place every Annotation.X.set(...) before the Type.make... step.

    @performance ― O(value size) encode, once at schema-definition time.

    set
    ({
    icon: string

    Sprite icon name (e.g., 'ph--user--regular', 'ph--cube--regular', 'px--anthropic--regular').

    ph--* is Phosphor; px--* and dx--* are brand glyphs, which are sprite-only and carry no weight variants. All three are admitted because a type whose subject IS a brand — an Anthropic session, a GitHub repo — has no honest Phosphor equivalent.

    icon
    : 'ph--tray-arrow-down--regular',
    hue?: string | undefined

    Color name.

    List of colors:

    • 'red'
    • 'orange'
    • 'amber'
    • 'yellow'
    • 'lime'
    • 'green'
    • 'emerald'
    • 'teal'
    • 'cyan'
    • 'violet'
    • 'purple'
    • 'fuchsia'
    • 'pink'
    • 'rose'

    hue
    : 'indigo',
    }),
    import Annotation
    Annotation
    .
    const UserType: Omit<Annotation.Annotation<Annotation.UserTypeValue>, "set"> & {
    set: (value?: Annotation.UserTypeValue) => <S extends Schema.Top>(schema: S) => S;
    }
    export UserType

    Marks a static type as user-facing, so it shows in pickers, the nav tree, and collections. Absent, the type is internal (like a dotfile, visible only via an advanced setting); a new type stays out of sight until someone opts it in. Stored as property meta, so it survives persisting the schema.

    @example

    Schema.Struct({ ... }).pipe(Annotation.UserType.set());
    Schema.Struct({ ... }).pipe(Annotation.UserType.set({ tags: [Collection.ItemTag] }));

    UserType
    .
    set: (value?: Annotation.UserTypeValue) => <S extends Schema.Top>(schema: S) => S
    set
    (),
    ),
    ) {}
  2. Add a factory. make fills in defaults, so every caller creates a link the same way.

    src/types/StashedLink.ts
    import {
    import Annotation
    Annotation
    ,
    import DXN
    DXN
    ,
    import Obj
    Obj
    ,
    import Type
    Type
    } from '@dxos/echo';
    19 collapsed lines
    export class
    class StashedLink
    StashedLink
    extends
    import Type
    Type
    .
    const makeObject: <StashedLink>(dxn: DXN.DXN, options?: {
    id?: EntityId;
    }) => Type.MakeObject<StashedLink>

    Factory function to create an ECHO object type.

    Returns a Type.Type entity — a live, in-memory TypeSchema instance, NOT a Schema.Schema. Use Type.InstanceType<typeof Foo> for the instance type and Type.getSchema(Foo) to obtain the underlying Effect Schema.

    The entity's id defaults to EntityId.deterministic(typename, version) so constructing a type never reaches crypto.getRandomValues() — required for Cloudflare workerd, which forbids RNG calls in global (module-evaluation) scope. Pass { id } to override (e.g. with EntityId.random() from a request handler).

    Giving the type a nominal class name keeps its inferred type portable for declaration emit: structural expansion (which can pull in unnameable union members from referenced schemas) is replaced by a reference to the class.

    The second call takes either a schema, or struct fields (wrapped in Schema.Struct) with an optional function that pipes annotations onto the struct.

    @example

    export class Person extends Type.makeObject<Person>(DXN.make('com.example.type.myType', '0.1.0'))(Schema.Struct({
    name: Schema.String,
    })) {}
    export class Tag extends Type.makeObject<Tag>(DXN.make('com.example.type.tag', '0.1.0'))(
    { label: Schema.String },
    Annotation.LabelAnnotation.set(['label']),
    ) {}

    @performance ― O(schema size) once at definition time; builds the type entity eagerly, so call it at module scope, not per use.

    makeObject
    <
    class StashedLink
    StashedLink
    >(
    import DXN
    DXN
    .
    const make: <"com.example.type.stashedLink">(nsid: "com.example.type.stashedLink", version?: string) => DXN.DXN

    Constructs a DXN from an NSID (and optional version). Throws if the result is not a valid DXN. Use tryMake for non-throwing string parsing.

    Static NSID strings are validated at compile time via

    Name

    : the final segment must be camelCase (no hyphens). Template-literal strings with a runtime prefix are accepted here but still validated at runtime.

    @example make('org.dxos.type.calendar') → 'dxn:org.dxos.type.calendar'

    @example make('org.dxos.type.calendar', '1.0.0') → 'dxn:org.dxos.type.calendar:1.0.0'

    make
    ('com.example.type.stashedLink', '0.1.0'),
    )(
    import Schema
    Schema
    .
    function Struct<{
    readonly title: Schema.optional<Schema.String>;
    readonly url: Schema.optional<Schema.String>;
    readonly note: Schema.optional<Schema.String>;
    readonly read: Schema.optional<Schema.Boolean>;
    }>(fields: {
    readonly title: Schema.optional<Schema.String>;
    readonly url: Schema.optional<Schema.String>;
    readonly note: Schema.optional<Schema.String>;
    readonly read: Schema.optional<Schema.Boolean>;
    }): Schema.Struct<{
    readonly title: Schema.optional<Schema.String>;
    readonly url: Schema.optional<Schema.String>;
    readonly note: Schema.optional<Schema.String>;
    readonly read: Schema.optional<Schema.Boolean>;
    }>

    Defines a struct schema from a map of field schemas.

    Details

    Each field value is a schema. Use

    optionalKey

    or

    optional

    to mark fields as optional, and

    mutableKey

    to mark them as mutable.

    The resulting schema's Type is a readonly object type with the fields' decoded types. The Encoded form mirrors the field schemas' encoded types. Declared fields may be inherited and are copied to own properties in the output. The __proto__ field is accepted only when it is an own property. Parsing does not guarantee that output keys retain their input order.

    Example (Defining a basic struct)

    import { Schema } from "effect"
    const Person = Schema.Struct({
    name: Schema.String,
    age: Schema.Number,
    email: Schema.optionalKey(Schema.String)
    })
    // { readonly name: string; readonly age: number; readonly email?: string }
    type Person = typeof Person.Type
    Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 }) // => { name: "Alice", age: 30 }

    @stability ― stable

    @category ― constructors

    @since ― 3.10.0

    Struct
    ({
    title: Schema.optional<Schema.String>
    title
    :
    import Schema
    Schema
    .
    const optional: optionalLambda
    <Schema.String>(self: Schema.String) => Schema.optional<Schema.String>

    Type-level representation returned by

    optional

    .

    Marks a struct field as optional, allowing the key to be absent or undefined.

    Details

    The resulting property may be absent or explicitly set to undefined. Equivalent to optionalKey(UndefinedOr(S)).

    Use

    optionalKey

    instead if you want exact optional semantics (absent only, not undefined).

    Example (Defining an optional field accepting undefined)

    import { Schema } from "effect"
    const schema = Schema.Struct({
    name: Schema.String,
    age: Schema.optional(Schema.Number)
    })
    // { readonly name: string; readonly age?: number | undefined }
    type Person = typeof schema.Type

    @stability ― stable

    @category ― models

    @since ― 3.10.0

    @stability ― stable

    @category ― combinators

    @since ― 3.10.0

    optional
    (
    import Schema
    Schema
    .
    const String: Schema.String

    Type-level representation of

    String

    .

    Schema for string values. Validates that the input is typeof "string".

    @stability ― stable

    @category ― models

    @since ― 4.0.0

    @stability ― stable

    @category ― schemas

    @since ― 4.0.0

    String
    .
    BottomWithoutNew<string, string, never, never, String, String, string, string, readonly [], string, "readonly", "required", "no-default", "readonly", "required">.annotate(annotations: Schema.Annotations.Bottom<string, readonly []>): Schema.String
    annotate
    ({
    Annotations.Augment.title?: string | undefined
    title
    : 'Title' })),
    url: Schema.optional<Schema.String>
    url
    :
    import Schema
    Schema
    .
    const optional: optionalLambda
    <Schema.String>(self: Schema.String) => Schema.optional<Schema.String>

    Type-level representation returned by

    optional

    .

    Marks a struct field as optional, allowing the key to be absent or undefined.

    Details

    The resulting property may be absent or explicitly set to undefined. Equivalent to optionalKey(UndefinedOr(S)).

    Use

    optionalKey

    instead if you want exact optional semantics (absent only, not undefined).

    Example (Defining an optional field accepting undefined)

    import { Schema } from "effect"
    const schema = Schema.Struct({
    name: Schema.String,
    age: Schema.optional(Schema.Number)
    })
    // { readonly name: string; readonly age?: number | undefined }
    type Person = typeof schema.Type

    @stability ― stable

    @category ― models

    @since ― 3.10.0

    @stability ― stable

    @category ― combinators

    @since ― 3.10.0

    optional
    (
    import Schema
    Schema
    .
    const String: Schema.String

    Type-level representation of

    String

    .

    Schema for string values. Validates that the input is typeof "string".

    @stability ― stable

    @category ― models

    @since ― 4.0.0

    @stability ― stable

    @category ― schemas

    @since ― 4.0.0

    String
    .
    BottomWithoutNew<string, string, never, never, String, String, string, string, readonly [], string, "readonly", "required", "no-default", "readonly", "required">.annotate(annotations: Schema.Annotations.Bottom<string, readonly []>): Schema.String
    annotate
    ({
    Annotations.Augment.title?: string | undefined
    title
    : 'URL' })),
    note: Schema.optional<Schema.String>
    note
    :
    import Schema
    Schema
    .
    const optional: optionalLambda
    <Schema.String>(self: Schema.String) => Schema.optional<Schema.String>

    Type-level representation returned by

    optional

    .

    Marks a struct field as optional, allowing the key to be absent or undefined.

    Details

    The resulting property may be absent or explicitly set to undefined. Equivalent to optionalKey(UndefinedOr(S)).

    Use

    optionalKey

    instead if you want exact optional semantics (absent only, not undefined).

    Example (Defining an optional field accepting undefined)

    import { Schema } from "effect"
    const schema = Schema.Struct({
    name: Schema.String,
    age: Schema.optional(Schema.Number)
    })
    // { readonly name: string; readonly age?: number | undefined }
    type Person = typeof schema.Type

    @stability ― stable

    @category ― models

    @since ― 3.10.0

    @stability ― stable

    @category ― combinators

    @since ― 3.10.0

    optional
    (
    import Schema
    Schema
    .
    const String: Schema.String

    Type-level representation of

    String

    .

    Schema for string values. Validates that the input is typeof "string".

    @stability ― stable

    @category ― models

    @since ― 4.0.0

    @stability ― stable

    @category ― schemas

    @since ― 4.0.0

    String
    .
    BottomWithoutNew<string, string, never, never, String, String, string, string, readonly [], string, "readonly", "required", "no-default", "readonly", "required">.annotate(annotations: Schema.Annotations.Bottom<string, readonly []>): Schema.String
    annotate
    ({
    Annotations.Augment.title?: string | undefined
    title
    : 'Note' })),
    read: Schema.optional<Schema.Boolean>
    read
    :
    import Schema
    Schema
    .
    const optional: optionalLambda
    <Schema.Boolean>(self: Schema.Boolean) => Schema.optional<Schema.Boolean>

    Type-level representation returned by

    optional

    .

    Marks a struct field as optional, allowing the key to be absent or undefined.

    Details

    The resulting property may be absent or explicitly set to undefined. Equivalent to optionalKey(UndefinedOr(S)).

    Use

    optionalKey

    instead if you want exact optional semantics (absent only, not undefined).

    Example (Defining an optional field accepting undefined)

    import { Schema } from "effect"
    const schema = Schema.Struct({
    name: Schema.String,
    age: Schema.optional(Schema.Number)
    })
    // { readonly name: string; readonly age?: number | undefined }
    type Person = typeof schema.Type

    @stability ― stable

    @category ― models

    @since ― 3.10.0

    @stability ― stable

    @category ― combinators

    @since ― 3.10.0

    optional
    (
    import Schema
    Schema
    .
    const Boolean: Schema.Boolean

    Type-level representation of

    Boolean

    .

    Schema for boolean values. Validates that the input is typeof "boolean".

    When to use

    Use to validate values that are already JavaScript booleans.

    @stability ― stable

    @category ― models

    @since ― 4.0.0

    @see ― BooleanFromBit for a schema that decodes bit literals 0 or 1 into a boolean

    @stability ― stable

    @category ― schemas

    @since ― 4.0.0

    Boolean
    .
    BottomWithoutNew<boolean, boolean, never, never, Boolean, Boolean, boolean, boolean, readonly [], boolean, "readonly", "required", "no-default", "readonly", "required">.annotate(annotations: Schema.Annotations.Bottom<boolean, readonly []>): Schema.Boolean
    annotate
    ({
    Annotations.Augment.title?: string | undefined
    title
    : 'Read' })),
    }).
    Pipeable.pipe<Schema.Struct<{
    readonly title: Schema.optional<Schema.String>;
    readonly url: Schema.optional<Schema.String>;
    readonly note: Schema.optional<Schema.String>;
    readonly read: Schema.optional<Schema.Boolean>;
    }>, Schema.Struct<{
    readonly title: Schema.optional<Schema.String>;
    readonly url: Schema.optional<Schema.String>;
    readonly note: Schema.optional<Schema.String>;
    readonly read: Schema.optional<Schema.Boolean>;
    }>, Schema.Struct<...>, Schema.Struct<...>>(this: Schema.Struct<...>, ab: (_: Schema.Struct<...>) => Schema.Struct<...>, bc: (_: Schema.Struct<...>) => Schema.Struct<...>, cd: (_: Schema.Struct<...>) => Schema.Struct<...>): Schema.Struct<...> (+21 overloads)
    pipe
    (
    import Annotation
    Annotation
    .
    const LabelAnnotation: AnnotationHelper<string[]>
    export LabelAnnotation
    LabelAnnotation
    .
    AnnotationHelper<string[]>.set: (value: string[]) => <S extends Schema.Top>(schema: S) => S

    Set the annotation on an Effect schema.

    Only accepts Schema.Top — annotations must be applied to the source schema BEFORE wrapping it with Type.makeObject / Type.makeRelation. In a pipe, place every Annotation.X.set(...) before the Type.make... step.

    set
    (['title']),
    import Annotation
    Annotation
    .
    const IconAnnotation: Annotation.Annotation<Annotation.IconAnnotation>
    export IconAnnotation

    Icon to render in the UI.

    IconAnnotation
    .
    Annotation<IconAnnotation>.set: (value: Annotation.IconAnnotation) => <S extends Schema.Top>(schema: S) => S

    Set the annotation on an Effect schema.

    Only accepts Schema.Top — annotations must be applied to the source schema BEFORE wrapping it with Type.makeObject / Type.makeRelation. In a pipe, place every Annotation.X.set(...) before the Type.make... step.

    @performance ― O(value size) encode, once at schema-definition time.

    set
    ({
    icon: string

    Sprite icon name (e.g., 'ph--user--regular', 'ph--cube--regular', 'px--anthropic--regular').

    ph--* is Phosphor; px--* and dx--* are brand glyphs, which are sprite-only and carry no weight variants. All three are admitted because a type whose subject IS a brand — an Anthropic session, a GitHub repo — has no honest Phosphor equivalent.

    icon
    : 'ph--tray-arrow-down--regular',
    hue?: string | undefined

    Color name.

    List of colors:

    • 'red'
    • 'orange'
    • 'amber'
    • 'yellow'
    • 'lime'
    • 'green'
    • 'emerald'
    • 'teal'
    • 'cyan'
    • 'violet'
    • 'purple'
    • 'fuchsia'
    • 'pink'
    • 'rose'

    hue
    : 'indigo',
    }),
    import Annotation
    Annotation
    .
    const UserType: Omit<Annotation.Annotation<Annotation.UserTypeValue>, "set"> & {
    set: (value?: Annotation.UserTypeValue) => <S extends Schema.Top>(schema: S) => S;
    }
    export UserType

    Marks a static type as user-facing, so it shows in pickers, the nav tree, and collections. Absent, the type is internal (like a dotfile, visible only via an advanced setting); a new type stays out of sight until someone opts it in. Stored as property meta, so it survives persisting the schema.

    @example

    Schema.Struct({ ... }).pipe(Annotation.UserType.set());
    Schema.Struct({ ... }).pipe(Annotation.UserType.set({ tags: [Collection.ItemTag] }));

    UserType
    .
    set: (value?: Annotation.UserTypeValue) => <S extends Schema.Top>(schema: S) => S
    set
    (),
    ),
    ) {}
    export const
    const make: (props?: Partial<Obj.MakeProps<typeof StashedLink>>) => StashedLink
    make
    = (
    props: Partial<Obj.MakeProps<typeof StashedLink>>
    props
    :
    type Partial<T> = { [P in keyof T]?: T[P] | undefined; }

    Make all properties in T optional

    Partial
    <
    import Obj
    Obj
    .
    type MakeProps<S extends Type.AnyObj> = {
    id?: EntityId;
    [Parent]?: Obj.Unknown;
    } & ([Exclude<keyof Type.InstanceType<S>, "~@dxos/echo/Kind" | "id" | typeof Source | typeof Target>] extends [never] ? Record<string, unknown> : Properties<Type.InstanceType<S>>)

    Props type for object creation with a given type. Accepts a Type.AnyObj entity and derives the instance shape via Type.InstanceType. Relation-kind entities are rejected at the type level — use Relation.MakeProps for those.

    When the schema is the unconstrained Type.AnyObj (Obj<unknown> — e.g. a dynamic type from schemaRegistry.register), the instance shape is not statically known, so data props widen to Record<string, unknown> and the caller can pass arbitrary fields without a cast.

    MakeProps
    <typeof
    class StashedLink
    StashedLink
    >> = {},
    ):
    class StashedLink
    StashedLink
    =>
    import Obj
    Obj
    .
    function make<typeof StashedLink>(type: typeof StashedLink, props: NoInfer<Obj.MakeProps<typeof StashedLink>>, options?: Obj.MakeOptions): Obj.OfShape<StashedLink & OfKind<EntityKind.Object>>

    Creates a new echo object of the given schema or Type.Type.

    @param ― typeOrSchema - A static object schema (Type.makeObject(...)) or a Type.Type entity (e.g. one returned by db.addType(schemaEntity)).

    @param ― props - Object properties.

    @param ―

    options - See MakeOptions.

    Meta can be passed as a symbol in props.

    Example:

    const obj = Obj.make(Person, { [Obj.Meta]: { keys: [...] }, name: 'John' });

    Note: Only accepts object schemas / object-kind types, not relation schemas. Use Relation.make for relations.

    @performance ― O(n) in props size; validates props against the schema and allocates a reactive proxy, with no database I/O.

    make
    (
    class StashedLink
    StashedLink
    , {
    title?: string | undefined
    title
    :
    props: Partial<Obj.MakeProps<typeof StashedLink>>
    props
    .
    title?: string | undefined
    title
    ,
    url?: string | undefined
    url
    :
    props: Partial<Obj.MakeProps<typeof StashedLink>>
    props
    .
    url?: string | undefined
    url
    ,
    note?: string | undefined
    note
    :
    props: Partial<Obj.MakeProps<typeof StashedLink>>
    props
    .
    note?: string | undefined
    note
    ?? '',
    read?: boolean | undefined
    read
    :
    props: Partial<Obj.MakeProps<typeof StashedLink>>
    props
    .
    read?: boolean | undefined
    read
    ?? false,
    });
  3. Register the type with the app. The schema module tells the app the type exists, so it can query, store and name objects of it.

    src/capabilities/schema.ts
    import {
    class StashedLink
    StashedLink
    } from '../types/StashedLink.ts';
    export default [
    class StashedLink
    StashedLink
    ];
  4. Name the type. The app labels a type with strings from the type’s own namespace, its typename. Add an entry for it to the translations, after the plugin’s own.

    src/translations.ts
    import {
    import Type
    Type
    } from '@dxos/echo';
    3 collapsed lines
    import type * as
    import Theme
    Theme
    from '@dxos/react-ui/Theme';
    import {
    const StashNamespace: {
    key: "com.example.plugin.stash";
    make: <T>() => <const N extends string>(name: N) => MultiTag<T, `com.example.plugin.stash.capability.${N}`>;
    makeSingleton: <T>() => <const N extends string>(name: N) => Tag<T, `com.example.plugin.stash.capability.${N}`>;
    }
    StashNamespace
    } from './namespace.ts';
    import {
    class StashedLink
    StashedLink
    } from './types/StashedLink.ts';
    8 collapsed lines
    export const
    const translations: [{
    readonly 'en-US': {
    readonly [x: string]: {
    readonly 'plugin.name': "Stash";
    readonly 'mark-read.label': "Mark as read";
    readonly 'typename.label'?: undefined;
    readonly 'typename.label_zero'?: undefined;
    readonly 'typename.label_one'?: undefined;
    readonly 'typename.label_other'?: undefined;
    readonly 'object-name.placeholder'?: undefined;
    readonly 'add-object.label'?: undefined;
    } | {
    readonly 'typename.label': "Stashed link";
    readonly 'typename.label_zero': "Stashed links";
    readonly 'typename.label_one': "Stashed link";
    readonly 'typename.label_other': "Stashed links";
    readonly 'object-name.placeholder': "New link";
    readonly 'add-object.label': "Add link";
    readonly 'plugin.name'?: undefined;
    readonly 'mark-read.label'?: undefined;
    };
    readonly "com.example.plugin.stash": {
    ...;
    };
    };
    }]
    translations
    = [
    {
    'en-US': {
    [
    const StashNamespace: {
    key: "com.example.plugin.stash";
    make: <T>() => <const N extends string>(name: N) => MultiTag<T, `com.example.plugin.stash.capability.${N}`>;
    makeSingleton: <T>() => <const N extends string>(name: N) => Tag<T, `com.example.plugin.stash.capability.${N}`>;
    }
    StashNamespace
    .
    key: "com.example.plugin.stash"
    key
    ]: {
    'plugin.name': 'Stash',
    'mark-read.label': 'Mark as read',
    },
    [
    import Type
    Type
    .
    const getTypename: (input: Type.AnyEntity) => string

    @returns ―

    The typename. Example: com.example.type.person.

    Persisted Type.Type entities carry typename in EntityMeta.key (the canonical registry-provenance field); unnamed drafts fall back to the entity's object id so the helper always returns a string. Any dxn: or echo:/ prefix is stripped — typename is a bare identifier, not a URI.

    @performance ― O(1) meta read with jsonSchema and id fallbacks.

    getTypename
    (
    class StashedLink
    StashedLink
    )]: {
    'typename.label': 'Stashed link',
    'typename.label_zero': 'Stashed links',
    'typename.label_one': 'Stashed link',
    'typename.label_other': 'Stashed links',
    'object-name.placeholder': 'New link',
    'add-object.label': 'Add link',
    },
  5. Add the module to the manifest.

    dx.config.ts
    import * as
    import AppCapability
    AppCapability
    from '@dxos/app-toolkit/AppCapability';
    19 collapsed lines
    import * as
    import AppSurface
    AppSurface
    from '@dxos/app-toolkit/AppSurface';
    import * as
    import SpaceCapability
    SpaceCapability
    from '@dxos/plugin-space/SpaceCapability';
    import {
    const StashNamespace: {
    key: "com.example.plugin.stash";
    make: <T>() => <const N extends string>(name: N) => MultiTag<T, `com.example.plugin.stash.capability.${N}`>;
    makeSingleton: <T>() => <const N extends string>(name: N) => Tag<T, `com.example.plugin.stash.capability.${N}`>;
    }
    StashNamespace
    } from './src/namespace.ts';
    const
    const config: Plugin.PluginConfig<void>
    config
    :
    import Plugin
    Plugin
    .
    type PluginConfig<T = void> = {
    readonly plugin: {
    readonly key: string;
    readonly name: string;
    readonly description?: string | undefined;
    readonly author?: string | undefined;
    readonly homePage?: string | undefined;
    readonly source?: string | undefined;
    readonly screenshots?: readonly {
    readonly light?: string | undefined;
    readonly dark?: string | undefined;
    }[] | undefined;
    readonly tags?: readonly string[] | undefined;
    readonly icon?: {
    readonly key: string;
    readonly hue?: string | undefined;
    } | undefined;
    readonly spec?: string | undefined;
    readonly dependsOn?: readonly string[] | undefined;
    };
    readonly publish?: {
    ...;
    } | undefined;
    } & {
    ...;
    }

    A plugin's dx.config.ts default export: its config data, runtime

    Meta

    and factory.

    PluginConfig
    =
    import Plugin
    Plugin
    .
    const config: <void>({ modules, ...data }: Plugin.ConfigOptions) => Plugin.PluginConfig<void>

    Defines a plugin entirely from its dx.config.ts.

    @example

    // dx.config.ts
    export default Plugin.config({
    plugin: { key: 'org.dxos.plugin.markdown', name: 'Markdown' },
    modules: [AppCapability.schema(() => import('./src/capabilities/schema.ts'))],
    });

    config
    ({
    plugin: {
    readonly key: string;
    readonly name: string;
    readonly description?: string | undefined;
    readonly author?: string | undefined;
    readonly homePage?: string | undefined;
    readonly source?: string | undefined;
    readonly screenshots?: readonly {
    readonly light?: string | undefined;
    readonly dark?: string | undefined;
    }[] | undefined;
    readonly tags?: readonly string[] | undefined;
    readonly icon?: {
    readonly key: string;
    readonly hue?: string | undefined;
    } | undefined;
    readonly spec?: string | undefined;
    readonly dependsOn?: readonly string[] | undefined;
    }
    plugin
    : {
    key: string

    Reverse-domain NSID — the plugin's globally-unique key (e.g. org.dxos.plugin.excalidraw).

    key
    :
    const StashNamespace: {
    key: "com.example.plugin.stash";
    make: <T>() => <const N extends string>(name: N) => MultiTag<T, `com.example.plugin.stash.capability.${N}`>;
    makeSingleton: <T>() => <const N extends string>(name: N) => Tag<T, `com.example.plugin.stash.capability.${N}`>;
    }
    StashNamespace
    .
    key: "com.example.plugin.stash"
    key
    ,
    name: string
    name
    : 'Stash',
    author?: string | undefined

    Author or organization name. Only used for bundled plugins. For plugins published to the registry this field is ignored — the verified publisher (handle ?? did) is used instead.

    author
    : 'Example',
    description?: string | undefined
    description
    :
    'A reading list: save links with a note, and mark them as read.',
    icon?: {
    readonly key: string;
    readonly hue?: string | undefined;
    } | undefined
    icon
    : {
    key: string
    key
    : 'ph--tray-arrow-down--regular',
    hue?: string | undefined
    hue
    : 'indigo' },
    tags?: readonly string[] | undefined
    tags
    : ['labs'],
    },
    modules: readonly (Module<any> | undefined)[]

    Module specs only; each body is a dynamic import, so reading the config loads no plugin code.

    modules
    : [
    import AppCapability
    AppCapability
    .
    const translations: (resources: Resource | Resource[] | (() => Promise<{
    translations: Resource | Resource[];
    }>), options?: {
    name?: string;
    environments?: readonly Environment[];
    }) => Module<void>

    Module contributing translations.

    translations
    (
    () => import('./src/translations.ts', {
    ImportCallOptions.with?: ImportAttributes | undefined
    with
    : {
    type: string
    type
    : 'module-url' } }),
    ),
    import AppCapability
    AppCapability
    .
    const schema: (types: ReadonlyArray<AnyEntity> | (() => Promise<{
    default: ReadonlyArray<AnyEntity>;
    }>), options?: {
    name?: string;
    environments?: readonly Environment[];
    }) => Module<void>

    Module contributing schemas. Prefer the loader form — schema objects ride whatever barrels declare them, so an inline list drags those barrels into the plugin definition's static closure (the boot evaluation floor); a loader keeps them in the module body chunk.

    schema
    (
    () =>
    import('./src/capabilities/schema.ts', {
    ImportCallOptions.with?: ImportAttributes | undefined
    with
    : {
    type: string
    type
    : 'module-url' },
    }),
    ),
  6. See the type. Click Load dev plugin below, or Reload if your plugin is loaded already, and select Schema. It lists the Stashed link type with its fields.

    There is no way to create a link yet. Creating one runs an operation, and the next page adds operations.

  • Declare a type with the annotations the app reads.
  • Register the type so the app can store and name it.
  • Name the type in the plugin’s translations.