composer-plugin-dev

star 507

Author DXOS Composer plugins — primarily community plugins built in their own repo (Vite + composerPlugin, GitHub release, registered via dxos/community-plugins), with notes on how the in-repo workflow differs. Use when scaffolding a new Composer plugin, wiring capabilities (surfaces, operations, blueprints), exposing operations to AI agents, integrating external services, testing with the composer testing harness, or publishing to the community registry.

dxos By dxos schedule Updated 6/5/2026

name: composer-plugin-dev description: Author DXOS Composer plugins — primarily community plugins built in their own repo (Vite + composerPlugin, GitHub release, registered via dxos/community-plugins), with notes on how the in-repo workflow differs. Use when scaffolding a new Composer plugin, wiring capabilities (surfaces, operations, skills), exposing operations to AI agents, integrating external services, testing with the composer testing harness, or publishing to the community registry.

Composer Plugin Authoring

Audience. This skill is for authors building a Composer plugin outside the dxos monorepo. For v1, the recommended layout is an external monorepo containing many community plugins (so refactoring/abstractions can move across them); standalone per-plugin repos like dxos/plugin-excalidraw are also supported and may become the preferred shape in v2. Inside the dxos monorepo (packages/plugins/plugin-*), most patterns are identical, but a handful of things change (build system, dep specifiers, package layout, registration, spec language). Each section below has an "Inside the dxos monorepo" callout where it differs.

Reference plugins.

When in doubt, copy a working plugin and modify.


How to use this skill

Each topic below links to a focused reference file under references/. Read the index first, then jump to the file you need. Files are short and self-contained; don't try to read them all at once.

Read before-you-build.md first. It covers the architectural decisions that should precede any scaffolding: surveying existing plugins, reusing internal types, preferring headless sync layers over bespoke UI, sync-state design, the open question around non-atomic writes, and when to stop and ask a human.

Topics

Before you build

  1. Before you build — survey existing plugins, reuse internal types, prefer headless sync over bespoke UI, build reusable sync infrastructure, watch out for non-atomic writes, and know when to flag architectural decisions to a human.

Getting started

  1. Scaffolding a community plugin — minimum viable repo: package.json, vite.config.ts, tsconfig.json, src/plugin.tsx, src/meta.ts. What to clone from plugin-excalidraw.
  2. Directory structure — the canonical src/ layout (capabilities/, components/, containers/, types/, operations/, skills/, translations.ts, meta.ts, plugin.tsx).
  3. Plugin definitionPlugin.define(meta).pipe(...) with AppPlugin.add*Module helpers. Activation events. The wiring order that works.

UI

  1. Components vs containers — the non-negotiable separation. src/components/ are presentational primitives with no @dxos/app-framework or @dxos/app-toolkit dependency. src/containers/ consume capabilities, are surface-aware, and are always lazy-loaded. Why this split exists and how it keeps your plugin testable.
  2. Components — named exports only, one subdir per component, basic storybook each.
  3. Containers & UI primitivesPanel.Root / Panel.Toolbar / Panel.Content / ScrollArea / Toolbar / Card patterns. Never write custom layout classNames when a primitive exists. Article/Card/Section role suffix conventions.
  4. React surfaceSurface.create() with AppSurface.object(role, Type) filters. Common roles: article, section, card--content, object-properties, dialog.

Data

  1. ECHO types & schemasType.makeObject({ typename, version }), LabelAnnotation, Annotation.IconAnnotation, namespace re-export (export * as Foo from './Foo'), make() factory using Obj.make(), FormInputAnnotation.
  2. Translations — keyed by typename (object labels) and meta.id (plugin-scoped strings). useTranslation(meta.id) in components.

Behavior — keep it out of the UI

  1. Operations vs UI: where logic belongsput large computations, side effects, and external-service integrations into operations, not UI code. UI components stay thin. Operations are testable, schema-typed, reusable from skills, callable from CLI, and re-runnable.
  2. OperationsOperation.make({ meta, input, output, services }), Operation.withHandler(Effect.fn(...)), OperationHandlerSet.lazy(). Splitting definitions.ts from per-handler files. Why services declare what they need.
  3. External services & authentication (AccessToken) — store credentials as AccessToken ECHO objects referenced by Ref.Ref<AccessToken>; load inside an operation using an Effect helper. Never read raw secrets in containers. Worked example modeled after plugin-inbox.
  4. AI inference (AiService) — when you need an LLM call, do it inside an operation with AiService.model('ai.claude.model.claude-sonnet-4-5'), tools via OpaqueToolkit, executor via ToolExecutionService. Don't call models from React.
  5. Skills — let agents use your plugin — define a skill key, gather your operations, Skill.toolDefinitions({ operations }), write a short instruction template. Every plugin worth writing should ship a skill so the assistant can drive it. How to register via addSkillDefinitionModule.
  6. CapabilitiesCapability.lazy() in capabilities/index.ts, Capability.makeModule() per file, Capability.contributes(Capabilities.X, ...). Why everything is lazy.

Packaging & publishing

  1. package.json — community-plugin form (single bundled module, deps pinned to a Composer release tag) vs. monorepo form (workspace:*, multiple exports, imports aliases, "private": true). The CLI entrypoint contract: cli must export only skill, operations, and types — no React, no app-framework UI capabilities — so it can run under Node.
  2. vite.config.ts (community plugins)composerPlugin({ entry: 'src/plugin.tsx', meta }) from @dxos/app-framework/vite-plugin, plus react() and wasm(). Emits dist/plugin.mjs and dist/manifest.json.
  3. moon.yml (in-repo only)compile.args lists one --entryPoint per package.json exports subpath. Skip if you're outside the monorepo.
  4. Publishing to the community registry — full release workflow:
    • GitHub Actions build → release with manifest.json + plugin.mjs assets.
    • Pin all @dxos/* deps to the Composer host's main dist-tag.
    • Local testing via Composer Settings → Plugins → Load by URL.
    • Submit a PR to dxos/community-plugins adding { "repo": "owner/repo" } to community-plugins.json.
    • Registry syncs into Composer roughly every 5 minutes.

Quality

  1. Testing with the composer harnesscreateComposerTestApp({ plugins: [...] }) from @dxos/plugin-testing/harness. Two minimum tests every plugin should have: a smoke test (modules activate on the right events) and an operation test (harness.invoke(MyOp, input)). Use the CLI variant of ClientPlugin (the main one references browser-only capabilities). Storybook for containers.
  2. Coding styleinvariant over throws; barrel imports (#capabilities, #components, etc.); no default exports except container index.ts (for React.lazy); ESM #private over TS private in new code; reactive ECHO (useQuery, useObject, atoms).
  3. Build & verify — community: pnpm build, pnpm test. Monorepo: moon run plugin-foo:build|lint|test|test-storybook.

Inside the dxos monorepo (only)

  1. PLUGIN.mdl specification — the design-first spec language (MDL). The PLUGIN.mdl is the design document. Approve before writing code; update before changing features. Template at packages/plugins/plugin-spec/docs/PLUGIN-.template.mdl. Community plugins can adopt this voluntarily, but it isn't required.
  2. Registering with composer-app — for in-repo plugins only: add to the composer app's plugin list. Community plugins are loaded dynamically by Composer from the registry; no registration step.

Quick decision tree

  • "Where do I put this code?" Heavy logic → operations/. External call → operations/ (with AccessToken). LLM call → operations/ (with AiService). Layout → containers/ using Panel/ScrollArea/Toolbar/Card. Reusable presentational → components/ (no framework deps).
  • "Should agents be able to do this?" If yes — and the answer is usually yes — define an Operation and reference it from a Skill.
  • "Where does this belong in package.json exports?" Browser/UI surfaces → main entry. Headless logic the CLI/agents need → ./skills, ./operations, ./types. Keep ./cli free of React.
  • "Do I need moon.yml?" Only inside the monorepo.

Common mistakes

  • Importing @dxos/app-framework from src/components/. (Use containers/.)
  • Calling fetch / external APIs / LLMs directly from a container. (Move to an operation.)
  • Skipping the skill, leaving the assistant unable to drive the plugin.
  • Writing custom Tailwind layout instead of using Panel / ScrollArea / Toolbar / Card.
  • Adding non-lazy exports to capabilities/index.ts.
  • Putting React imports in the CLI entrypoint.
  • Hard-coding secrets instead of using AccessToken Refs.
  • Pinning @dxos/* deps to arbitrary versions instead of the Composer host's main dist-tag.
Install via CLI
npx skills add https://github.com/dxos/dxos --skill composer-plugin-dev
Repository Details
star Stars 507
call_split Forks 43
navigation Branch main
article Path SKILL.md
More from Creator