Plugin Development Steps
This document provides a detailed practical guide from a starter template to publishing a plugin. The sample project is based on an nx monorepo template (see directory structure below). Contents cover initialization, implementation, config schema, testing, building, publishing, and common troubleshooting.
1. Prerequisites
Before getting started, ensure your environment meets the following:
- Node.js (LTS recommended >= 18)
- npm or pnpm
- nx tooling (no global install required — use via npx nx)
- TypeScript
- A configured monorepo (the project template already includes one)
It's recommended to install dependencies before development:
npm install
# or
pnpm install
2. Template project structure
The provided template directory (excerpt) is:
.
├── AGENTS.md
├── CHANGELOG.md
├── CLAUDE.md
├── README.md
├── TREE.txt
├── jest.config.ts
├── jest.preset.js
├── nx.json
├── package-lock.json
├── package.json
├── packages
│   └── my-plugin
│       ├── README.md
│       ├── jest.config.js
│       ├── package.json
│       ├── src
│       │   ├── index.ts
│       │   └── lib
│       │       ├── my-plugin.spec.ts
│       │       ├── my-plugin.ts
│       │       ├── tool.ts
│       │       ├── toolset.strategy.ts
│       │       └── types.ts
│       ├── tsconfig.json
│       └── tsconfig.lib.json
├── tsconfig.base.json
├── tsconfig.json
└── tsconfig.spec.json
Note: The template places each plugin under packages/<name>. Libraries generated by @nx/js:lib with --publishable can be built and published independently to npm.
3. Quick start (create a plugin with Nx)
Run in the repo root to create a new plugin library:
npx nx g @nx/js:lib packages/my-plugin --publishable --importPath=@my-org/my-plugin
Common build and publish commands:
# build library
npx nx build my-plugin
# use the monorepo release flow provided by the template
npx nx release
# publish to npm manually (can pass otp)
npx nx run @my-org/my-plugin:nx-release-publish --access public --otp=<one-time-password-if-needed>
Note: --importPath should match the name field in package.json to make external imports consistent.
3.1 Creating Multiple Plugins
# Create the first plugin
npx nx g @nx/js:lib packages/plugin-a --publishable --importPath=@xpert-ai/plugin-a
# Create the second plugin
npx nx g @nx/js:lib packages/plugin-b --publishable --importPath=@xpert-ai/plugin-b
# Create the third plugin
npx nx g @nx/js:lib packages/plugin-c --publishable --importPath=@xpert-ai/plugin-c
3.2 Example Project Structure
packages/
├── my-plugin/           # Existing example plugin
├── weather-plugin/      # Weather plugin
├── calculator-plugin/   # Calculator plugin
├── file-processor/      # File processor plugin
└── ai-assistant/        # AI assistant plugin
4. Plugin entry (index.ts)
The plugin entry is the first point where the host loads the plugin. It typically exports a default XpertPlugin object.
Key points:
- Use zodto define the pluginconfigschema (for validation and automatic UI form generation).
- metacontains plugin metadata (name/version/category/icon/displayName/description/keywords).
- register(ctx)returns the plugin's module class (to be injected into the NestJS app).
- Optionally implement onStart/onStoplifecycle hooks.
The template provides an index.ts example: it defines ConfigSchema, meta, register, onStart, and onStop.
Recommendations:
- Use a scoped package name for meta.name(e.g.,@xpert-ai/my-plugin) and keep it consistent withpackage.json.
- Prefer using an SVG string or data URL for iconto ease front-end rendering.
5. Implement the plugin module (MyPlugin) and lifecycle hooks
The plugin module class is declared with the @XpertServerPlugin decorator: it will be imported and registered in the host application.
Key fields:
- imports: other modules (e.g.,- RouterModule.register([{ path: '/xxx', module: MyPlugin }]))
- entities: ORM/TypeORM entity classes array (if the plugin needs persistence)
- providers: services/strategies injected into the container
- controllers: controllers that expose routes
Lifecycle interfaces:
- IOnPluginBootstrap:- onPluginBootstrap()is called when the plugin is initialized (good for one-time registration/checks).
- IOnPluginDestroy:- onPluginDestroy()is called when the plugin is unloaded/destroyed.
Implementation tips:
- Use ctx.logger(provided by the host) for startup/shutdown logs instead ofconsole.log.
- Avoid long blocking operations during module load; perform necessary initialization asynchronously in onPluginBootstrap.
6. Implementing Strategies (Toolset / Integration / DocumentSource)
Strategies are the core of the plugin extension system. Common types include:
- ToolsetStrategy (tool collections, e.g., calculators, transformers)
- IntegrationStrategy (external system integrations, e.g., Firecrawl, third-party APIs)
- DocumentSourceStrategy (document data sources, e.g., web crawling, S3, databases)
Below are examples using Calculator (toolset) and Firecrawl (document source / integration).
6.1 ToolsetStrategy (Calculator example)
The @ToolsetStrategy(MyToolName) decorator marks a class as a toolset provider. Primary responsibilities: provide metadata, validate config, and create toolsets.
- meta: includes name, label, description, icon, configSchema, etc., for UI listing and config forms.
- validateConfig(config): validate the provided configuration.
- create(config): returns a- BuiltinToolsetinstance (a reusable toolset).
- createTools(): alternatively returns an array of tools if not using- BuiltinToolset.
In CalculatorStrategy, a CalculatorToolset is created and depends on buildCalculatorTool() to build individual tools.
6.2 IntegrationStrategy
Models an external service as a configurable integration in the host. Responsibilities:
- Provide meta(including schema for rendering integration config fields in the UI).
- Implement execute(integration, payload)to perform an integration operation or trigger a task.
Security note: fields for API keys in meta.schema should use ISchemaSecretField to instruct the front end to mask/encrypt and allow persistence (persist: true).
6.3 DocumentSourceStrategy
Converts external documents/data sources to the host-indexable Document[] (e.g., LangChain Document). Core methods:
- validateConfig(config): validate configuration.
- test(config): test connectivity.
- loadDocuments(config, context?): return- Document[], each containing- pageContentand- metadata(e.g., source, title, description).
In the example, FirecrawlSourceStrategy.loadDocuments calls firecrawlService.crawlUrl(context.integration, config) and maps results to Document.
7. Services and Controllers
Plugins usually register services and controllers in the NestJS container:
- Service: responsible for communication with external APIs (with retry/timeout logic and rate limiting).
- Controller: exposes HTTP endpoints for testing/debugging (e.g., POST /test).
Example: FirecrawlController.connect accepts an IIntegration payload and calls firecrawlService.test.
Implementation notes:
- Set reasonable timeouts and handle errors for external requests.
- For long-running tasks (crawling, bulk imports), use a job queue (Bull, Queue) and return a task id instead of blocking HTTP requests.
- Use @I18nLang()or other i18n tools in services/controllers for multi-language messages.
8. Config schema and UI metadata (zod + JSON Schema)
Recommendation: define plugin configuration using zod for strong typing and convenience. To render config forms in the UI, convert zod schemas to JSON Schema or maintain a JSON Schema in meta.schema.
Example:
const ConfigSchema = z.object({
  apiUrl: z.string().url().optional(),
  apiKey: z.string().optional()
})
9. Unit and integration tests
The template includes my-plugin.spec.ts. Recommended practices:
- Use Jest and Nest testing utilities (@nestjs/testing) to create module contexts.
- Unit test Strategy methods like validateConfig,create, andcreateTools.
- Mock external HTTP calls (nock / msw) to test Services.
- Write e2e-style request tests for Controllers using supertest.
Run tests:
npx nx test my-plugin
10. Build, release, and npm publish flow
Build
npx nx build my-plugin
After building, the output (e.g., packages/my-plugin/dist or the configured output directory) will contain publishable files.
Versioning and publishing
The template provides an nx release flow (refer to the repo's release script). Typical steps:
- Update versioninpackage.json(or use the release script to bump automatically).
- npx nx build my-plugin
- npx nx release(if configured, it may create a release based on CHANGELOG/commits)
Manual publish:
npx nx run @my-org/my-plugin:nx-release-publish --access public --otp=<otp-if-needed>
Tip: Ensure package.json fields like files, main, and types point to built outputs; use publishConfig to set access if needed.
11. Runtime behavior and debugging tips
Plugin load and runtime notes:
- The host calls the plugin's registerand injects the returned module into the Nest app. Ensureregisterreturns{ module: YourPluginModule, global: true|false }.
- Use onStart/onStopfor runtime initialization and teardown.
- IOnPluginBootstrapand- IOnPluginDestroycomplement the host framework's lifecycle hooks.
Debugging tips:
- Enable verbose logs: using ctx.logger.debug/info/errorinregisteroronStartwrites to the host logging system.
- Check DI errors: common "Nest can't resolve dependencies" errors mean a provider wasn't registered in providers.
- Check entity registration: missing or duplicated entitiescan cause ORM errors.
12. Security and best practices
- Do not hardcode keys or credentials in source code.
- Mark secret fields with ISchemaSecretFieldand let the host handle encryption/secure storage.
- Add retry, timeout, and circuit-breaker strategies for external calls.
- Use queues for long tasks to avoid blocking request lifecycles.
- Enable strict TypeScript and be explicit with types (especially generics like IIntegration<T>).
- Single responsibility: each plugin should focus on one category of functionality (e.g., integration or data source) for maintainability and access control.
13. Common issues and troubleshooting
- Issue: Plugin fails to load with - Cannot find module- Check name/mainfields inpackage.json.
- Ensure the build output contains index.js.
 
- Check 
- Issue: Nest DI injection fails - Verify the provider is registered in @XpertServerPlugin.providers.
- Check constructor parameters use @Inject()or proper type annotations.
 
- Verify the provider is registered in 
- Issue: Entities duplicate registration or migration errors - Make sure entitiesdoesn't include duplicate classes and paths/namespaces match.
 
- Make sure 
- Issue: API Key cannot be displayed or saved in the UI - Verify meta.schemausesISchemaSecretFieldwithpersist: true.
 
- Verify 
14. Appendix: example files and common commands
14.1 Key example commands
# initialize plugin library
npx nx g @nx/js:lib packages/my-plugin --publishable --importPath=@xpert-ai/my-plugin
# build
npx nx build my-plugin
# test
npx nx test my-plugin
# release (release script)
npx nx release
# manual publish
npx nx run @xpert-ai/my-plugin:nx-release-publish --access public --otp=<otp>
14.2 Common code snippets (overview)
- index.ts: exports the- XpertPluginobject
- my-plugin.ts: module class using- @XpertServerPlugin
- toolset.strategy.ts: implements a- ToolsetStrategy
- tool.ts: constructs actual tools (- buildCalculatorTool())
- types.ts: plugin-related constants and types
(The template already contains concrete implementations which you can modify as needed.)
Summary
This chapter provides an end-to-end guide from creating a plugin scaffold to publishing, covering Strategy implementation, Services, Controllers, config schema, security, testing, building, and publishing. You can start quickly from the template and harden your plugin using the "best practices" and "troubleshooting" sections above.
If you like, I can:
- Complete a runnable implementation of buildCalculatorTool();
- Or implement a full example FirecrawlService (HTTP calls, error handling, retries);
- Or expand sections into longer tutorials or example files (e.g., generate src/lib/tool.ts,service.tsfiles).
Tell me which you'd like me to do next (for example: generate a runnable tool.ts, implement FirecrawlService, or turn this document into an English/Chinese README).