Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 80 additions & 3 deletions docs/docs/development/templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,14 @@ The plugin manifest drives the CLI's behavior during `databricks apps init`:
- **`app.yaml` generation** — resource fields produce `env` + `valueFrom` entries
- **`databricks.yml` generation** — resource fields produce bundle variables and app resource entries

The synced manifest is generated by `appkit plugin sync --write` from each plugin's `manifest.json` (see [Plugin manifest](../plugins/manifest.md) for the authoring contract). The on-disk shape carries a `version` field that the CLI uses to negotiate features:

- `"1.0"` / `"1.1"` — earlier shapes; still readable.
- `"2.0"` — current shape. Adds `scaffolding` (required) and the `origin` field on every resource field entry. JSON Schema published at `https://databricks.github.io/appkit/schemas/template-plugins.schema.json`.

### Resource field properties

Each resource field in the manifest can have these properties:
Each resource field in the synced manifest can have these properties:

| Property | Description |
|----------|-------------|
Expand All @@ -63,6 +68,21 @@ Each resource field in the manifest can have these properties:
| `value` | Default value used when no user input is provided |
| `resolve` | Auto-populated by CLI from API calls instead of prompting (see below) |
| `examples` | Example values shown in field descriptions |
| `discovery` | How the CLI lists candidate values for the field (see [Plugin manifest — Resource discovery](../plugins/manifest.md#resource-discovery)). |
| `origin` | **v2.0** computed field. How the value is determined — see below. |

### `origin` (v2.0)

`origin` is computed at sync time from the field's other properties — plugin authors do not write it. It tells scaffolding agents how each value reaches the running app:

| Origin | Trigger | Meaning |
|--------|---------|---------|
| `"platform"` | `localOnly: true` | Auto-injected by Databricks Apps at deploy time. Generated for local `.env` only; absent from `app.yaml` and bundle variables. |
| `"static"` | `value` set | Hardcoded literal. CLI does not prompt. |
| `"cli"` | `resolve` set | Resolved by the CLI from API calls (e.g. `postgres:host`). |
| `"user"` | none of the above | User must provide the value at init time. |

Precedence is in the order above (`localOnly` wins over `value`, which wins over `resolve`). The transform overwrites any hand-edited `origin` on the next sync — drift between the on-disk value and the field's actual shape is not possible by construction.

### Resolvers

Expand All @@ -89,7 +109,64 @@ Example field definition:
}
```

After sync, the field carries `"origin": "platform"` (because `localOnly` takes precedence over `resolve` for local-only fields injected at deploy time).

### `postScaffold` propagation

Each plugin's `postScaffold` array is propagated unchanged into its entry in `appkit.plugins.json`. The CLI surfaces these instructions to the user after init runs. See [Post-scaffold steps](../plugins/manifest.md#post-scaffold-steps) for the field shape.

### `scaffolding` descriptor (v2.0)

The `scaffolding` block at the top level of `appkit.plugins.json` describes the scaffolding command and the rules a scaffolding agent must follow. It is required when `version` is `"2.0"`.

```json
{
"scaffolding": {
"command": "databricks apps init",
"flags": {
"--template-dir": {
"description": "Path to the template directory containing the app scaffold",
"required": true
},
"--config-dir": {
"description": "Path to the output directory for the initialized app",
"required": true
},
"--profile": {
"description": "Databricks CLI profile to use for authentication",
"required": false
}
},
"rules": {
"never": [
"Modify files inside the template directory",
"Skip resource configuration prompts",
"Hardcode workspace-specific values in template files"
],
"must": [
"Use the template manifest (appkit.plugins.json) as the source of truth for available plugins",
"Respect requiredByTemplate flags when presenting plugin selection",
"Generate .env files with all required environment variables from selected plugins",
"When discovering volume resources, prompt the user for catalog and schema before listing volumes."
]
}
}
}
```

| Field | Description |
|-------|-------------|
| `command` | Scaffolding command the agent should invoke. |
| `flags` | Map of flag name to `{ description, required?, pattern?, default? }`. |
| `rules.never` | Actions the scaffolding agent must never perform. |
| `rules.must` | Actions the scaffolding agent must always perform. |

Each rule item is capped at **120 characters** by the schema. Long prose fails validation — split into discrete actionable directives.

The descriptor is canonical: AppKit owns the values and ships them with every synced template manifest. Authors of consuming agents (LLM-driven scaffolders, custom CLI runners) should treat the rule lists as enforcement contracts, not suggestions.

## See also

- [Plugin management](../plugins/plugin-management.md) — `appkit plugin sync`, `appkit plugin create`
- [Configuration](../configuration.mdx) — environment variables
- [Plugin manifest](../plugins/manifest.md) — the authoring side (`manifest.json`).
- [Plugin management](../plugins/plugin-management.md) — `appkit plugin sync`, `appkit plugin create`.
- [Configuration](../configuration.mdx) — environment variables.
55 changes: 32 additions & 23 deletions docs/docs/plugins/custom-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,34 +18,41 @@ For a deeper understanding of the plugin structure, read on.

## Basic plugin example

Extend the [`Plugin`](../api/appkit/Class.Plugin.md) class and export with `toPlugin()`:
Author the manifest as JSON, import it, and attach it to a [`Plugin`](../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 type express from "express";
import manifest from "./manifest.json";

class MyPlugin extends Plugin {
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: []
}
} satisfies PluginManifest<"myPlugin">;
static manifest = manifest as PluginManifest<"my-plugin">;

async setup() {
// Initialize your plugin
Expand All @@ -69,6 +76,8 @@ class MyPlugin extends Plugin {
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, post-scaffold steps), see [Plugin manifest](./manifest.md).

## Config-dependent resources

The manifest defines resources as either `required` (always needed) or `optional` (may be needed).
Expand Down
Loading