# Creating custom plugins

If you need custom API routes or background logic, implement an AppKit plugin. The fastest way is to use the CLI:

```bash
# Interactive
npx @databricks/appkit plugin create

# Non-interactive
npx @databricks/appkit plugin create --placement in-repo --path plugins/my-plugin --name my-plugin --description "My plugin" --force

```

For a deeper understanding of the plugin structure, read on.

## Basic plugin example[​](#basic-plugin-example "Direct link to Basic plugin example")

Author the manifest as JSON, import it, and attach it to a [`Plugin`](./docs/api/appkit/Class.Plugin.md) subclass via `static manifest`. Export with `toPlugin()`:

```json
// my-plugin/manifest.json
{
  "$schema": "https://databricks.github.io/appkit/schemas/plugin-manifest.schema.json",
  "name": "my-plugin",
  "displayName": "My Plugin",
  "description": "A custom plugin",
  "resources": {
    "required": [
      {
        "type": "secret",
        "alias": "apiKey",
        "resourceKey": "api-key",
        "description": "API key for external service",
        "permission": "READ",
        "fields": {
          "scope": { "env": "MY_SECRET_SCOPE", "description": "Secret scope" },
          "key": { "env": "MY_API_KEY", "description": "Secret key name" }
        }
      }
    ],
    "optional": []
  }
}

```

```typescript
// my-plugin/index.ts
import { Plugin, toPlugin, type PluginManifest } from "@databricks/appkit";
import manifest from "./manifest.json";

class MyPlugin extends Plugin {
  static manifest = manifest as PluginManifest<"my-plugin">;

  async setup() {
    // Initialize your plugin
  }

  myCustomMethod() {
    // Some implementation
  }

  async shutdown() {
    // Clean up resources
  }

  exports() {
    return {
      myCustomMethod: this.myCustomMethod
    }
  }
}

export const myPlugin = toPlugin(MyPlugin);

```

JSON is the canonical authoring surface — it is what `appkit plugin sync` reads when aggregating manifests for templates. For the full v2.0 manifest contract (resources, discovery descriptors, scaffolding rules), see [Plugin manifest](./docs/plugins/manifest.md).

## Config-dependent resources[​](#config-dependent-resources "Direct link to Config-dependent resources")

The manifest defines resources as either `required` (always needed) or `optional` (may be needed). For resources that become required based on plugin configuration, implement a static `getResourceRequirements(config)` method:

```typescript
interface MyPluginConfig extends BasePluginConfig {
  enableCaching?: boolean;
}

class MyPlugin extends Plugin<MyPluginConfig> {
  static manifest = {
    name: "myPlugin",
    displayName: "My Plugin",
    description: "A plugin with optional caching",
    resources: {
      required: [
        { type: "sql_warehouse", alias: "warehouse", resourceKey: "sqlWarehouse", description: "Query execution", permission: "CAN_USE", fields: { id: { env: "DATABRICKS_WAREHOUSE_ID" } } }
      ],
      optional: [
        // Listed as optional in manifest for static analysis
        { type: "database", alias: "cache", resourceKey: "cache", description: "Query result caching (if enabled)", permission: "CAN_CONNECT_AND_CREATE", fields: { instance_name: { env: "DATABRICKS_CACHE_INSTANCE" }, database_name: { env: "DATABRICKS_CACHE_DB" } } }
      ]
    }
  } satisfies PluginManifest<"myPlugin">;

  // Runtime: Convert optional resources to required based on config
  static getResourceRequirements(config: MyPluginConfig) {
    const resources = [];
    if (config.enableCaching) {
      // When caching is enabled, Database becomes required
      resources.push({
        type: "database",
        alias: "cache",
        resourceKey: "cache",
        description: "Query result caching",
        permission: "CAN_CONNECT_AND_CREATE",
        fields: {
          instance_name: { env: "DATABRICKS_CACHE_INSTANCE" },
          database_name: { env: "DATABRICKS_CACHE_DB" },
        },
        required: true  // Mark as required at runtime
      });
    }
    return resources;
  }
}

```

This pattern allows:

* **Static tools** (CLI, docs) to show all possible resources
* **Runtime validation** to enforce resources based on actual configuration

## Key extension points[​](#key-extension-points "Direct link to Key extension points")

* **Route injection**: Implement `injectRoutes()` to add custom endpoints using [`IAppRouter`](./docs/api/appkit/TypeAlias.IAppRouter.md)

* **Lifecycle hooks**: Override `setup()`, and `shutdown()` methods

* **Shared services**:

  <!-- -->

  * **Cache management**: Access the cache service via `this.cache`. See [`CacheConfig`](./docs/api/appkit/Interface.CacheConfig.md) for configuration.
  * **Telemetry**: Instrument your plugin with traces and metrics via `this.telemetry`. See [`ITelemetry`](./docs/api/appkit/Interface.ITelemetry.md).

* **Execution interceptors**: Use `execute()` and `executeStream()` with [`StreamExecutionSettings`](./docs/api/appkit/Interface.StreamExecutionSettings.md) for automatic caching, retry, timeout, and [telemetry span attributes](./docs/plugins/execution-context.md#telemetry-span-attributes) (`execution.context`, `caller.id`)

**Consuming your plugin programmatically**

Optionally, you may want to provide a way to consume your plugin programmatically using the AppKit object. To do that, your plugin needs to implement the `exports` method, returning an object with the methods you want to expose. From the previous example, the plugin could be consumed as follows:

```ts
const AppKit = await createApp({
  plugins: [
    server({ port: 8000 }),
    analytics(),
    myPlugin(),
  ],
});

AppKit.myPlugin.myCustomMethod();

```

See the [`Plugin`](./docs/api/appkit/Class.Plugin.md) API reference for complete documentation.
