Skip to main content
⚠️ AppKit is in an early development phase and is subject to change without notice. DO NOT use in production environments.

# Creating custom plugins

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

    npx @databricks/appkit plugin create

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

## Basic plugin example​

Extend the `Plugin` class and export with `toPlugin()`:

    import { Plugin, toPlugin } from "@databricks/appkit";
    import type express from "express";

    class MyPlugin extends Plugin {
      name = "myPlugin";

      // Define resource requirements in the static manifest
      static manifest = {
        name: "myPlugin",
        displayName: "My Plugin",
        description: "A custom plugin",
        resources: {
          required: [
            {
              type: "secret",
              alias: "apiKey",
              resourceKey: "apiKey",
              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: []
        }
      };

      async setup() {
        // Initialize your plugin
      }

      myCustomMethod() {
        // Some implementation
      }

      async shutdown() {
        // Clean up resources
      }

      exports() {
        // an object with the methods from this plugin to expose
        return {
          myCustomMethod: this.myCustomMethod
        }
      }
    }

    export const myPlugin = toPlugin<typeof MyPlugin, Record<string, never>, "myPlugin">(
      MyPlugin,
      "myPlugin",
    );

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

    interface MyPluginConfig extends BasePluginConfig {
      enableCaching?: boolean;
    }

    class MyPlugin extends Plugin<MyPluginConfig> {
      name = "myPlugin";

      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" } } }
          ]
        }
      };

      // 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​

- Route injection: Implement `injectRoutes()` to add custom endpoints using `IAppRouter`
- Lifecycle hooks: Override `setup()`, and `shutdown()` methods
- Shared services:
  - Cache management: Access the cache service via `this.cache`. See `CacheConfig` for configuration.
  - Telemetry: Instrument your plugin with traces and metrics via `this.telemetry`. See `ITelemetry`.
- Execution interceptors: Use `execute()` and `executeStream()` with `StreamExecutionSettings`

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:

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

    AppKit.myPlugin.myCustomMethod();

See the `Plugin` API reference for complete documentation.