Skip to main content

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.

Plugin development template

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 plugin config 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 with package.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 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 of console.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 BuiltinToolset instance (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 pageContent and 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, and createTools.
  • 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:

  1. Update version in package.json (or use the release script to bump automatically).
  2. npx nx build my-plugin
  3. 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. Ensure register returns { module: YourPluginModule, global: true|false }.
  • Use onStart/onStop for runtime initialization and teardown.
  • IOnPluginBootstrap and IOnPluginDestroy complement the host framework's lifecycle hooks.

Debugging tips:

  • Enable verbose logs: using ctx.logger.debug/info/error in register or onStart 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 in package.json.
    • Ensure the build output contains index.js.
  • Issue: Nest DI injection fails

    • Verify the provider is registered in @XpertServerPlugin.providers.
    • Check constructor parameters use @Inject() or proper type annotations.
  • Issue: Entities duplicate registration or migration errors

    • Make sure entities doesn't include duplicate classes and paths/namespaces match.
  • Issue: API Key cannot be displayed or saved in the UI

    • Verify meta.schema uses ISchemaSecretField with persist: true.

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 XpertPlugin object
  • 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 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).