Skip to main content

Creating custom plugins

Creating custom plugins

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

# 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

Author the manifest as JSON, import it, and attach it to a Plugin subclass via static manifest. Export with toPlugin():

// 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": []
}
}
// 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.

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> {
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

  • 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 for automatic caching, retry, timeout, and 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:

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

AppKit.myPlugin.myCustomMethod();

See the Plugin API reference for complete documentation.

Databricks Developer Hub

Ready to ship your next agentic app in minutes?

Read docs