Skip to content

ECHO

A plugin stores its data as ECHO objects in the person’s spaces. The types themselves are ordinary ECHO types, which Defining types covers. This page covers what a plugin adds so the app knows about them.

The app can store, query and show an object only if its type is registered. A plugin registers its types with the Schema capability: a module returns the list, and AppCapability.schema contributes it.

src/capabilities/schema.ts
import {
class StashedLink
StashedLink
} from '../types/StashedLink.ts';
export default [
class StashedLink
StashedLink
];
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' },
}),
),

A type registered by no plugin still syncs, since ECHO stores any object, but the app cannot name, list or create it.

Annotations on the schema tell the app how to present an object:

  • LabelAnnotation names the property that holds the object’s label, shown in the navigation tree and in search.
  • IconAnnotation gives the type an icon and a hue.
  • UserType marks a type people create themselves, so it appears in the app’s create menus. Leave it off types that only a plugin creates, such as a sync cursor.
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
(),
),
) {}

The type’s name in the menus comes from the plugin’s translations, keyed by its typename. See App capabilities.

Give each type a make function that fills in defaults, so every plugin that creates one gets a valid object.

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,
});

To add the type to the app’s create menu, contribute a SpaceCapabilities.CreateObjectEntry. The Space plugin lists every entry, and calls createObject with the properties the person entered and the place they chose. SpaceOperation.AddObject adds the object to the space, so the change can be undone and other plugins see it.

src/capabilities/create-object.ts
import * as
import Effect
Effect
from 'effect/Effect';
import * as
import Capability
Capability
from '@dxos/app-framework/Capability';
import * as
import Operation
Operation
from '@dxos/compute/Operation';
import {
import Type
Type
} from '@dxos/echo';
import * as
import SpaceCapabilities
SpaceCapabilities
from '@dxos/plugin-space/SpaceCapabilities';
import * as
import SpaceOperation
SpaceOperation
from '@dxos/plugin-space/SpaceOperation';
import {
class StashedLink
StashedLink
,
const make: (props?: Partial<MakeProps<typeof StashedLink>>) => StashedLink
make
} from '../types/StashedLink.ts';
export default
import Capability
Capability
.
const makeModule: <void, Capability.Contribution<Capability.CapabilityIdentifier<"org.dxos.plugin.space.capability.createObject", "multi">>, never, never>(fn: (props: void) => Effect.Effect<Capability.Contribution<Capability.CapabilityIdentifier<"org.dxos.plugin.space.capability.createObject", "multi">>, never, Scope>) => (props: void) => Effect.Effect<Capability.Contribution<Capability.CapabilityIdentifier<"org.dxos.plugin.space.capability.createObject", "multi">>, never, Scope>

Helper to define a capability module with explicit typing. Wraps the default export function to provide better type inference and make the pattern explicit.

This helper provides explicit typing for the module activation function, making it clear that the function should:

  • Access declared requires via yield* (or the ambient Capability.Service/Plugin.Service)
  • Return an array of typed

Contribution

s (see

contribute

/

contributeAll

)

@example

// Module without options - single capability
export default Capability.makeModule(
Effect.fnUntraced(function* () {
const client = yield* ClientCapabilities.Client;
return Capability.contribute(Capabilities.SettingsStore, store);
})
);
// Module with multiple capabilities
export default Capability.makeModule(
Effect.fnUntraced(function* () {
return [
Capability.contribute(Capabilities.SettingsStore, store),
Capability.contribute(Capabilities.Translations, translations),
];
})
);
// Module with required options (context accessed via layer)
export default Capability.makeModule(
Effect.fnUntraced(function* (props: { observability: boolean }) {
const invoker = yield* Capabilities.OperationInvoker;
return Capability.contribute(Capabilities.OperationHandler, ...);
})
);
// Module with scoped resources (closed automatically on deactivation)
export default Capability.makeModule(
Effect.fnUntraced(function* () {
const scope = yield* Scope.Scope;
yield* Scope.addFinalizer(scope, Effect.sync(() => cleanup()));
return Capability.contribute(Capabilities.MyCapability, implementation);
})
);

makeModule
(() =>
import Effect
Effect
.
const succeed: <Capability.Contribution<Capability.CapabilityIdentifier<"org.dxos.plugin.space.capability.createObject", "multi">>>(value: Capability.Contribution<Capability.CapabilityIdentifier<"org.dxos.plugin.space.capability.createObject", "multi">>) => Effect.Effect<Capability.Contribution<Capability.CapabilityIdentifier<"org.dxos.plugin.space.capability.createObject", "multi">>, never, never>

Creates an Effect that always succeeds with a given value.

When to use

Use when an effect should complete successfully with a specific value without any errors or external dependencies.

Example (Creating a successful effect)

import { Effect } from "effect"
// Creating an effect that represents a successful scenario
//
// ┌─── Effect<number, never, never>
// ▼
const success = Effect.succeed(42)
Effect.runSync(success) // => 42

@see ― fail to create an effect that represents a failure.

@stability ― stable

@category ― constructors

@since ― 2.0.0

succeed
(
import Capability
Capability
.
const contribute: <Capability.MultiTag<Readonly<{
id: string;
createObject: CreateObject;
inputSchema?: Codec<any, any>;
customPanel?: React.ComponentType<SpaceCapabilities.CreateObjectCustomPanelProps>;
}>, "org.dxos.plugin.space.capability.createObject">>(capability: Capability.MultiTag<Readonly<{
id: string;
createObject: CreateObject;
inputSchema?: Codec<any, any>;
customPanel?: React.ComponentType<SpaceCapabilities.CreateObjectCustomPanelProps>;
}>, "org.dxos.plugin.space.capability.createObject">, implementation: Readonly<...>) => Capability.Contribution<...> (+1 overload)
contribute
(
import SpaceCapabilities
SpaceCapabilities
.
const CreateObjectEntry: Capability.MultiTag<Readonly<{
id: string;
createObject: CreateObject;
inputSchema?: Codec<any, any>;
customPanel?: React.ComponentType<SpaceCapabilities.CreateObjectCustomPanelProps>;
}>, "org.dxos.plugin.space.capability.createObject">

Typed creation entry contributed per typename by plugins that support creating objects.

CreateObjectEntry
, {
id: string
id
:
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
),
createObject: CreateObject
createObject
: (
props: any
props
,
options: {
db: Database;
target?: Unknown;
targetNodeId?: string;
}
options
) =>
import Operation
Operation
.
const invoke: <{
readonly object: Unknown | {
readonly [x: string]: unknown;
readonly "@type": string;
};
readonly target?: import("@dxos/echo/internal").Ref<Unknown> | Unknown | undefined;
}, {
readonly id: string;
readonly object: Unknown;
}>(op: Operation.Definition<{
readonly object: Unknown | {
readonly [x: string]: unknown;
readonly "@type": string;
};
readonly target?: import("@dxos/echo/internal").Ref<Unknown> | Unknown | undefined;
}, {
readonly id: string;
readonly object: Unknown;
}, any>, input: {
readonly object: Unknown | {
readonly [x: string]: unknown;
readonly "@type": string;
};
readonly target?: import("@dxos/echo/internal").Ref<Unknown> | Unknown | undefined;
}, options?: Operation.InvokeOptions | undefined) => Effect.Effect<...>

Invoke an operation as an Effect. Yields the Operation.Service internally.

@example

yield* Operation.invoke(MyOperation, { data: 'test' });

invoke
(
import SpaceOperation
SpaceOperation
.
const AddObject: Operation.Definition<{
readonly object: Unknown | {
readonly [x: string]: unknown;
readonly "@type": string;
};
readonly target?: import("@dxos/echo/internal").Ref<Unknown> | Unknown | undefined;
}, {
readonly id: string;
readonly object: Unknown;
}, Capability.Service | Service>
AddObject
,
{
object: Unknown | {
readonly [x: string]: unknown;
readonly '@type': string;
}
object
:
function make(props?: Partial<MakeProps<typeof StashedLink>>): StashedLink
make
(
props: any
props
),
target?: Unknown | Ref<Unknown> | undefined
target
:
options: {
db: Database;
target?: Unknown;
targetNodeId?: string;
}
options
.
target?: Unknown | undefined

The created object's parent; absent files at the space root of db.

target
},
{
InvokeOptions.spaceId?: SpaceId | undefined

Space ID to provide database context for the handler.

spaceId
:
options: {
db: Database;
target?: Unknown;
targetNodeId?: string;
}
options
.
db: Database
db
.
Database.spaceId: SpaceId
spaceId
},
),
}),
),
);
  • A plugin can register only types it defines or depends on. It cannot change another plugin’s type.
  • Turning a plugin off does not delete its objects. They stay in the space, and the app shows them again once the plugin is back on.