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.
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',
17
description?: string | undefined
description:
18
'A reading list: save links with a note, and mark them as read.',
19
icon?: {
readonly key: string;
readonly hue?: string | undefined;
} | undefined
icon: {
key: string
key: 'ph--tray-arrow-down--regular',
hue?: string | undefined
hue: 'indigo' },
20
tags?: readonly string[] | undefined
tags: ['labs'],
21
},
22
modules: readonly (Module<any> | undefined)[]
Module specs only; each body is a dynamic import, so reading the config loads no plugin code.
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.
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.
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.
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.
AnnotationHelper<string[]>.set: (value: string[]) => <SextendsSchema.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.
Annotation<IconAnnotation>.set: (value: Annotation.IconAnnotation) => <SextendsSchema.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({
20
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.
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.
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.
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.
AnnotationHelper<string[]>.set: (value: string[]) => <SextendsSchema.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.
Annotation<IconAnnotation>.set: (value: Annotation.IconAnnotation) => <SextendsSchema.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({
20
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.
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.
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.
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(
classStashedLink
StashedLink, {
31
title?: string | undefined
title:
props: Partial<Obj.MakeProps<typeof StashedLink>>
props.
title?: string | undefined
title,
32
url?: string | undefined
url:
props: Partial<Obj.MakeProps<typeof StashedLink>>
props.
url?: string | undefined
url,
33
note?: string | undefined
note:
props: Partial<Obj.MakeProps<typeof StashedLink>>
props.
note?: string | undefined
note ?? '',
34
read?: boolean | undefined
read:
props: Partial<Obj.MakeProps<typeof StashedLink>>
props.
read?: boolean | undefined
read ?? false,
35
});
36
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.
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)
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.