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:
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.
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
zod
to define the pluginconfig
schema (for validation and automatic UI form generation). meta
contains 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
/onStop
lifecycle 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
icon
to 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 containercontrollers
: 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 aBuiltinToolset
instance (a reusable toolset).createTools()
: alternatively returns an array of tools if not usingBuiltinToolset
.
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?)
: returnDocument[]
, each containingpageContent
andmetadata
(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 c
After building, the output (e.g., packages/packages/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
version
inpackage.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
register
and injects the returned module into the Nest app. Ensureregister
returns{ module: YourPluginModule, global: true|false }
. - Use
onStart
/onStop
for runtime initialization and teardown. IOnPluginBootstrap
andIOnPluginDestroy
complement the host framework's lifecycle hooks.
Debugging tips:
- Enable verbose logs: using
ctx.logger.debug/info/error
inregister
oronStart
writes 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
entities
can cause ORM errors.
12. Security and best practicesโ
- Do not hardcode keys or credentials in source code.
- Mark secret fields with
ISchemaSecretField
and 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
/main
fields 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
entities
doesn't include duplicate classes and paths/namespaces match.
- Make sure
Issue: API Key cannot be displayed or saved in the UI
- Verify
meta.schema
usesISchemaSecretField
withpersist: 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 theXpertPlugin
objectmy-plugin.ts
: module class using@XpertServerPlugin
toolset.strategy.ts
: implements aToolsetStrategy
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
Firecrawl
Service (HTTP calls, error handling, retries); - Or expand sections into longer tutorials or example files (e.g., generate
src/lib/tool.ts
,service.ts
files).
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).