Define a type
By the end of this page the plugin has a Stashed link type, and the inspector lists its fields.
Background
Section titled “Background”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.
-
Declare the type.
Type.makeObjectgives 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 * asSchema from 'effect/Schema';import Schemaimport {Annotation,import AnnotationDXN,import DXNObj,import ObjType } from '@dxos/echo';import Typeexport classStashedLink extendsclass StashedLinkType.import TypemakeObject<const makeObject: <StashedLink>(dxn: DXN.DXN, options?: {id?: EntityId;}) => Type.MakeObject<StashedLink>Factory function to create an ECHO object type.
Returns a
Type.Typeentity — a live, in-memoryTypeSchemainstance, NOT aSchema.Schema. UseType.InstanceType<typeof Foo>for the instance type andType.getSchema(Foo)to obtain the underlying Effect Schema.The entity's id defaults to
EntityId.deterministic(typename, version)so constructing a type never reachescrypto.getRandomValues()— required for Cloudflare workerd, which forbids RNG calls in global (module-evaluation) scope. Pass{ id }to override (e.g. withEntityId.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.StashedLink>(class StashedLinkDXN.import DXNmake('com.example.type.stashedLink', '0.1.0'),const make: <"com.example.type.stashedLink">(nsid: "com.example.type.stashedLink", version?: string) => DXN.DXNConstructs a DXN from an NSID (and optional version). Throws if the result is not a valid DXN. Use
tryMakefor 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.
)(Schema.import SchemaStruct({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
Typeis a readonly object type with the fields' decoded types. TheEncodedform 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.TypeSchema.decodeUnknownSync(Person)({ name: "Alice", age: 30 }) // => { name: "Alice", age: 30 }title:title: Schema.optional<Schema.String>Schema.import Schemaoptional(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 tooptionalKey(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.TypeSchema.import SchemaString.const String: Schema.StringType-level representation of
String
.
Schema for
stringvalues. Validates that the input istypeof"string".annotate({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.Stringtitle: 'Title' })),Annotations.Augment.title?: string | undefinedurl:url: Schema.optional<Schema.String>Schema.import Schemaoptional(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 tooptionalKey(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.TypeSchema.import SchemaString.const String: Schema.StringType-level representation of
String
.
Schema for
stringvalues. Validates that the input istypeof"string".annotate({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.Stringtitle: 'URL' })),Annotations.Augment.title?: string | undefinednote:note: Schema.optional<Schema.String>Schema.import Schemaoptional(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 tooptionalKey(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.TypeSchema.import SchemaString.const String: Schema.StringType-level representation of
String
.
Schema for
stringvalues. Validates that the input istypeof"string".annotate({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.Stringtitle: 'Note' })),Annotations.Augment.title?: string | undefinedread:read: Schema.optional<Schema.Boolean>Schema.import Schemaoptional(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 tooptionalKey(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.TypeSchema.import SchemaBoolean.const Boolean: Schema.BooleanType-level representation of
Boolean
.
Schema for
booleanvalues. Validates that the input istypeof"boolean".When to use
Use to validate values that are already JavaScript booleans.
annotate({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.Booleantitle: 'Read' })),Annotations.Augment.title?: string | undefined}).pipe(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)Annotation.import AnnotationLabelAnnotation.const LabelAnnotation: AnnotationHelper<string[]>export LabelAnnotationset(['title']),AnnotationHelper<string[]>.set: (value: string[]) => <S extends Schema.Top>(schema: S) => SSet the annotation on an Effect schema.
Only accepts
Schema.Top— annotations must be applied to the source schema BEFORE wrapping it withType.makeObject/Type.makeRelation. In a pipe, place everyAnnotation.X.set(...)before theType.make...step.Annotation.import AnnotationIconAnnotation.const IconAnnotation: Annotation.Annotation<Annotation.IconAnnotation>export IconAnnotationIcon to render in the UI.
set({Annotation<IconAnnotation>.set: (value: Annotation.IconAnnotation) => <S extends Schema.Top>(schema: S) => SSet the annotation on an Effect schema.
Only accepts
Schema.Top— annotations must be applied to the source schema BEFORE wrapping it withType.makeObject/Type.makeRelation. In a pipe, place everyAnnotation.X.set(...)before theType.make...step.icon: 'ph--tray-arrow-down--regular',icon: stringSprite icon name (e.g., 'ph--user--regular', 'ph--cube--regular', 'px--anthropic--regular').
ph--*is Phosphor;px--*anddx--*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.hue: 'indigo',hue?: string | undefinedColor name.
List of colors:
- 'red'
- 'orange'
- 'amber'
- 'yellow'
- 'lime'
- 'green'
- 'emerald'
- 'teal'
- 'cyan'
- 'violet'
- 'purple'
- 'fuchsia'
- 'pink'
- 'rose'
}),Annotation.import AnnotationUserType.const UserType: Omit<Annotation.Annotation<Annotation.UserTypeValue>, "set"> & {set: (value?: Annotation.UserTypeValue) => <S extends Schema.Top>(schema: S) => S;}export UserTypeMarks 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.
set(),set: (value?: Annotation.UserTypeValue) => <S extends Schema.Top>(schema: S) => S),) {} -
Add a factory.
makefills in defaults, so every caller creates a link the same way.src/types/StashedLink.ts import {Annotation,import AnnotationDXN,import DXNObj,import ObjType } from '@dxos/echo';import Type19 collapsed linesexport classStashedLink extendsclass StashedLinkType.import TypemakeObject<const makeObject: <StashedLink>(dxn: DXN.DXN, options?: {id?: EntityId;}) => Type.MakeObject<StashedLink>Factory function to create an ECHO object type.
Returns a
Type.Typeentity — a live, in-memoryTypeSchemainstance, NOT aSchema.Schema. UseType.InstanceType<typeof Foo>for the instance type andType.getSchema(Foo)to obtain the underlying Effect Schema.The entity's id defaults to
EntityId.deterministic(typename, version)so constructing a type never reachescrypto.getRandomValues()— required for Cloudflare workerd, which forbids RNG calls in global (module-evaluation) scope. Pass{ id }to override (e.g. withEntityId.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.StashedLink>(class StashedLinkDXN.import DXNmake('com.example.type.stashedLink', '0.1.0'),const make: <"com.example.type.stashedLink">(nsid: "com.example.type.stashedLink", version?: string) => DXN.DXNConstructs a DXN from an NSID (and optional version). Throws if the result is not a valid DXN. Use
tryMakefor 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.
)(Schema.import SchemaStruct({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
Typeis a readonly object type with the fields' decoded types. TheEncodedform 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.TypeSchema.decodeUnknownSync(Person)({ name: "Alice", age: 30 }) // => { name: "Alice", age: 30 }title:title: Schema.optional<Schema.String>Schema.import Schemaoptional(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 tooptionalKey(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.TypeSchema.import SchemaString.const String: Schema.StringType-level representation of
String
.
Schema for
stringvalues. Validates that the input istypeof"string".annotate({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.Stringtitle: 'Title' })),Annotations.Augment.title?: string | undefinedurl:url: Schema.optional<Schema.String>Schema.import Schemaoptional(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 tooptionalKey(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.TypeSchema.import SchemaString.const String: Schema.StringType-level representation of
String
.
Schema for
stringvalues. Validates that the input istypeof"string".annotate({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.Stringtitle: 'URL' })),Annotations.Augment.title?: string | undefinednote:note: Schema.optional<Schema.String>Schema.import Schemaoptional(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 tooptionalKey(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.TypeSchema.import SchemaString.const String: Schema.StringType-level representation of
String
.
Schema for
stringvalues. Validates that the input istypeof"string".annotate({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.Stringtitle: 'Note' })),Annotations.Augment.title?: string | undefinedread:read: Schema.optional<Schema.Boolean>Schema.import Schemaoptional(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 tooptionalKey(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.TypeSchema.import SchemaBoolean.const Boolean: Schema.BooleanType-level representation of
Boolean
.
Schema for
booleanvalues. Validates that the input istypeof"boolean".When to use
Use to validate values that are already JavaScript booleans.
annotate({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.Booleantitle: 'Read' })),Annotations.Augment.title?: string | undefined}).pipe(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)Annotation.import AnnotationLabelAnnotation.const LabelAnnotation: AnnotationHelper<string[]>export LabelAnnotationset(['title']),AnnotationHelper<string[]>.set: (value: string[]) => <S extends Schema.Top>(schema: S) => SSet the annotation on an Effect schema.
Only accepts
Schema.Top— annotations must be applied to the source schema BEFORE wrapping it withType.makeObject/Type.makeRelation. In a pipe, place everyAnnotation.X.set(...)before theType.make...step.Annotation.import AnnotationIconAnnotation.const IconAnnotation: Annotation.Annotation<Annotation.IconAnnotation>export IconAnnotationIcon to render in the UI.
set({Annotation<IconAnnotation>.set: (value: Annotation.IconAnnotation) => <S extends Schema.Top>(schema: S) => SSet the annotation on an Effect schema.
Only accepts
Schema.Top— annotations must be applied to the source schema BEFORE wrapping it withType.makeObject/Type.makeRelation. In a pipe, place everyAnnotation.X.set(...)before theType.make...step.icon: 'ph--tray-arrow-down--regular',icon: stringSprite icon name (e.g., 'ph--user--regular', 'ph--cube--regular', 'px--anthropic--regular').
ph--*is Phosphor;px--*anddx--*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.hue: 'indigo',hue?: string | undefinedColor name.
List of colors:
- 'red'
- 'orange'
- 'amber'
- 'yellow'
- 'lime'
- 'green'
- 'emerald'
- 'teal'
- 'cyan'
- 'violet'
- 'purple'
- 'fuchsia'
- 'pink'
- 'rose'
}),Annotation.import AnnotationUserType.const UserType: Omit<Annotation.Annotation<Annotation.UserTypeValue>, "set"> & {set: (value?: Annotation.UserTypeValue) => <S extends Schema.Top>(schema: S) => S;}export UserTypeMarks 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.
set(),set: (value?: Annotation.UserTypeValue) => <S extends Schema.Top>(schema: S) => S),) {}export constmake = (const make: (props?: Partial<Obj.MakeProps<typeof StashedLink>>) => StashedLinkprops:props: Partial<Obj.MakeProps<typeof StashedLink>>Partial<type Partial<T> = { [P in keyof T]?: T[P] | undefined; }Make all properties in T optional
Obj.import ObjMakeProps<typeoftype 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.AnyObjentity and derives the instance shape viaType.InstanceType. Relation-kind entities are rejected at the type level — useRelation.MakePropsfor those.When the schema is the unconstrained
Type.AnyObj(Obj<unknown>— e.g. a dynamic type fromschemaRegistry.register), the instance shape is not statically known, so data props widen toRecord<string, unknown>and the caller can pass arbitrary fields without a cast.StashedLink>> = {},class StashedLink):StashedLink =>class StashedLinkObj.import Objmake(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.StashedLink, {class StashedLinktitle:title?: string | undefinedprops.props: Partial<Obj.MakeProps<typeof StashedLink>>title,title?: string | undefinedurl:url?: string | undefinedprops.props: Partial<Obj.MakeProps<typeof StashedLink>>url,url?: string | undefinednote:note?: string | undefinedprops.props: Partial<Obj.MakeProps<typeof StashedLink>>note ?? '',note?: string | undefinedread:read?: boolean | undefinedprops.props: Partial<Obj.MakeProps<typeof StashedLink>>read ?? false,read?: boolean | undefined}); -
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 {StashedLink } from '../types/StashedLink.ts';class StashedLinkexport default [StashedLink];class StashedLink -
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 {Type } from '@dxos/echo';import Type3 collapsed linesimport type * asTheme from '@dxos/react-ui/Theme';import Themeimport {StashNamespace } from './namespace.ts';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}`>;}import {StashedLink } from './types/StashedLink.ts';class StashedLink8 collapsed linesexport consttranslations = [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": {...;};};}]{'en-US': {[StashNamespace.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}`>;}key]: {key: "com.example.plugin.stash"'plugin.name': 'Stash','mark-read.label': 'Mark as read',},[Type.import TypegetTypename(const getTypename: (input: Type.AnyEntity) => stringStashedLink)]: {class 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',}, -
Add the module to the manifest.
dx.config.ts import * asAppCapability from '@dxos/app-toolkit/AppCapability';import AppCapability19 collapsed linesimport * asAppSurface from '@dxos/app-toolkit/AppSurface';import AppSurfaceimport * asSpaceCapability from '@dxos/plugin-space/SpaceCapability';import SpaceCapabilityimport {StashNamespace } from './src/namespace.ts';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}`>;}constconfig:const config: Plugin.PluginConfig<void>Plugin.import PluginPluginConfig =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.tsdefault export: its config data, runtimeMeta
and factory.
Plugin.import Pluginconfig({const config: <void>({ modules, ...data }: Plugin.ConfigOptions) => Plugin.PluginConfig<void>Defines a plugin entirely from its
dx.config.ts.plugin: {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;}key:key: stringReverse-domain NSID — the plugin's globally-unique key (e.g.
org.dxos.plugin.excalidraw).StashNamespace.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}`>;}key,key: "com.example.plugin.stash"name: 'Stash',name: stringauthor: 'Example',author?: string | undefinedAuthor 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.
description:description?: string | undefined'A reading list: save links with a note, and mark them as read.',icon: {icon?: {readonly key: string;readonly hue?: string | undefined;} | undefinedkey: 'ph--tray-arrow-down--regular',key: stringhue: 'indigo' },hue?: string | undefinedtags: ['labs'],tags?: readonly string[] | undefined},modules: [modules: readonly (Module<any> | undefined)[]Module specs only; each body is a dynamic import, so reading the config loads no plugin code.
AppCapability.import AppCapabilitytranslations(const translations: (resources: Resource | Resource[] | (() => Promise<{translations: Resource | Resource[];}>), options?: {name?: string;environments?: readonly Environment[];}) => Module<void>Module contributing translations.
() => import('./src/translations.ts', {with: {ImportCallOptions.with?: ImportAttributes | undefinedtype: 'module-url' } }),type: string),AppCapability.import AppCapabilityschema(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.
() =>import('./src/capabilities/schema.ts', {with: {ImportCallOptions.with?: ImportAttributes | undefinedtype: 'module-url' },type: string}),), -
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.
What you can do now
Section titled “What you can do now”- 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.