> ## Documentation Index
> Fetch the complete documentation index at: https://docs.xpertai.cn/llms.txt
> Use this file to discover all available pages before exploring further.

# 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.

<Tip>
  **Plugin development template**\
  git clone [https://github.com/xpert-ai/xpert-plugins-starter](https://github.com/xpert-ai/xpert-plugins-starter)
</Tip>

## 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](https://github.com/xpert-ai/xpert-plugins-starter) already includes one)

It's recommended to install dependencies before development:

```bash theme={null}
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:

```bash theme={null}
npx nx g @nx/js:lib packages/my-plugin --publishable --importPath=@my-org/my-plugin
```

Common build and publish commands:

```bash theme={null}
# 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

```bash theme={null}
# 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

```sh theme={null}
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 `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.

### 7.1 Webhook controllers and scoped credentials

If a plugin exposes a webhook endpoint for an external system, protect it with the SDK-provided `PluginWebhookAuthGuard` instead of asking users to place a full platform API key in the webhook URL.

The host manages a per-Integration webhook credential through `IntegrationPermissionService`:

* `ensureWebhookCredential(id, { provider })` creates or returns the active credential and returns the plain token once.
* `rotateWebhookCredential(id, { provider })` creates a replacement token.
* `revokeWebhookCredential(id, { provider })` disables the token.
* The Integration stores only `options.webhookCredential.tokenHash` plus metadata, never the plain token.

Build callback URLs with an opaque query secret:

```text theme={null}
https://api.example.com/api/my-provider/webhook/<integration-id>?secret=<webhook-secret>
```

Route pattern:

```ts theme={null}
import { Body, Controller, Param, Post, Request, UseGuards } from '@nestjs/common'
import { PluginWebhookAuth, PluginWebhookAuthGuard, runWithRequestContext } from '@xpert-ai/plugin-sdk'

@Controller('my-provider')
export class MyWebhookController {
  // Use the host public-route decorator when normal platform auth is global.
  @Public()
  @PluginWebhookAuth({
    provider: 'my-provider',
    integrationParam: 'integrationId',
    secretQueryParam: 'secret'
  })
  @UseGuards(PluginWebhookAuthGuard)
  @Post('webhook/:integrationId')
  async webhook(
    @Param('integrationId') integrationId: string,
    @Request() req: { user?: unknown; headers?: Record<string, string> },
    @Body() body: unknown
  ) {
    await new Promise<void>((resolve, reject) => {
      runWithRequestContext({ user: req.user, headers: req.headers ?? {} }, {}, () => {
        this.handleWebhook(integrationId, body).then(resolve).catch(reject)
      })
    })
    return 'success'
  }
}
```

Register the guard in the plugin module:

```ts theme={null}
@XpertServerPlugin({
  providers: [
    MyWebhookService,
    PluginWebhookAuthGuard
  ],
  controllers: [MyWebhookController]
})
export class MyProviderPlugin {}
```

Use the normal host public-route decorator when the webhook must bypass the global login guard. Do not add `PluginWebhookAuthGuard` as a global bootstrap guard; attach it only to webhook routes with `@UseGuards()`. Always set `provider` in `@PluginWebhookAuth()` so a credential issued for one Integration provider cannot be replayed against another provider route.

***

## 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:

```ts theme={null}
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:

```bash theme={null}
npx nx test my-plugin
```

***

## 10. Build, release, and npm publish flow

### Build

```bash theme={null}
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:

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:

```bash theme={null}
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

```bash theme={null}
# 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).
