# Templates

AppKit uses a template system powered by the Databricks CLI's `databricks apps init` command. Templates define the project structure, and `.tmpl` files are processed with Go's `text/template` engine to generate customized output.

## How `.tmpl` files work[​](#how-tmpl-files-work "Direct link to how-tmpl-files-work")

Any file ending in `.tmpl` is processed by the CLI during `databricks apps init`:

1. The `.tmpl` suffix is stripped (e.g. `.env.tmpl` → `.env`)
2. Go template expressions are evaluated and substituted
3. The rendered file is written to the output directory

Files named with a `_` prefix are renamed to `.` prefix (e.g. `_gitignore` → `.gitignore`).

### Template variables[​](#template-variables "Direct link to Template variables")

| Variable          | Description                                                                  |
| ----------------- | ---------------------------------------------------------------------------- |
| `.projectName`    | Project name from `--name` or interactive prompt                             |
| `.workspaceHost`  | Databricks workspace URL                                                     |
| `.profile`        | Databricks CLI profile name (empty if using host-based auth)                 |
| `.appDescription` | App description                                                              |
| `.plugins.<name>` | Non-nil for each selected plugin, enabling conditionals                      |
| `.dotEnv.content` | Generated `.env` content from plugin resources                               |
| `.dotEnv.example` | Generated `.env.example` content with placeholders                           |
| `.bundle.*`       | Generated `databricks.yml` sections (variables, resources, target variables) |
| `.appEnv`         | Generated `app.yaml` env entries                                             |

### Conditional content[​](#conditional-content "Direct link to Conditional content")

Use Go template conditionals to include/exclude code based on selected plugins:

```go
{{- if .plugins.analytics}}
import { analytics } from '@databricks/appkit';
{{- end}}

```

## `appkit.plugins.json`[​](#appkitpluginsjson "Direct link to appkitpluginsjson")

The plugin manifest drives the CLI's behavior during `databricks apps init`:

* **Plugin selection UI** — selectable plugins shown in the interactive prompt
* **Resource prompts** — required/optional resources prompt the user for values (e.g. SQL Warehouse ID)
* **`.dotEnv` population** — resource fields with an `env` property are written to `.env`
* **`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](./docs/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 by the CLI when `version` is `"2.0"`; enforced at parse time, not via the published JSON Schema) 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[​](#resource-field-properties "Direct link to Resource field properties")

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

| Property       | Description                                                                                                                                         |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `env`          | Environment variable name written to `.env` and `app.yaml`                                                                                          |
| `description`  | Shown in the interactive prompt and bundle variable description                                                                                     |
| `localOnly`    | Only written to `.env` for local dev; excluded from `app.yaml` and bundle variables                                                                 |
| `bundleIgnore` | Excluded from `databricks.yml` variables (but still in `.env`)                                                                                      |
| `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](./docs/plugins/manifest.md#resource-discovery)). |
| `origin`       | **v2.0** computed field. How the value is determined — see below.                                                                                   |

### `origin` (v2.0)[​](#origin-v20 "Direct link to origin-v20")

`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[​](#resolvers "Direct link to Resolvers")

Fields with a `resolve` property are auto-populated by the CLI from API calls rather than user prompts. The format is `<type>:<field>`.

Currently only the `postgres` resource type has a resolver. Given the user-provided `branch` and `database` resource names, it derives:

| Resolve key             | Description                                                 |
| ----------------------- | ----------------------------------------------------------- |
| `postgres:host`         | Postgres host from the branch's read-write endpoint         |
| `postgres:databaseName` | Postgres database name from the database resource           |
| `postgres:endpointPath` | Lakebase endpoint resource name from the branch's endpoints |

Example field definition:

```json
{
  "host": {
    "env": "PGHOST",
    "localOnly": true,
    "resolve": "postgres:host",
    "description": "Postgres host for local development."
  }
}

```

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

### `scaffolding.rules` propagation[​](#scaffoldingrules-propagation "Direct link to scaffoldingrules-propagation")

Each plugin's `scaffolding.rules` block (see [Plugin manifest — Scaffolding rules](./docs/plugins/manifest.md#scaffolding-rules)) is propagated unchanged into its entry in `appkit.plugins.json`. The CLI hands the merged plugin-level rules — alongside the top-level template `scaffolding.rules` — to scaffolding agents that drive `databricks apps init`.

Merge model:

1. Gather rules from every selected plugin and from every plugin with `requiredByTemplate: true`.
2. Apply the template-level `scaffolding.rules` on top.
3. Plugin-level rules **override** skill-baked or template-level defaults at the same directive site.
4. A plugin `must` that conflicts with a template `never` (or vice versa) stops the init flow — see the validation rules below.

### `scaffolding` descriptor (v2.0)[​](#scaffolding-descriptor-v20 "Direct link to scaffolding-descriptor-v20")

The `scaffolding` block at the top level of `appkit.plugins.json` describes the scaffolding command, its flags, and the cross-cutting rules every scaffolding agent must respect. It is required by the CLI when `version` is `"2.0"`. The requirement is enforced at parse time — the published JSON Schema marks only `version` and `plugins` as top-level required fields because it cannot express conditional requirements.

```json
{
  "scaffolding": {
    "command": "databricks apps init",
    "flags": {
      "--name": {
        "description": "Project name — sets {{.projectName}} in package.json, databricks.yml, and .env. Required for non-interactive scaffolding.",
        "required": true,
        "pattern": "^[a-z][a-z0-9-]*$"
      },
      "--features": {
        "description": "Plugins to enable (comma-separated, no spaces; must match keys in this manifest's plugins map)",
        "required": false,
        "pattern": "^[a-zA-Z0-9_-]+(,[a-zA-Z0-9_-]+)*$"
      },
      "--profile": {
        "description": "Databricks CLI profile to use for authentication (global flag)",
        "required": false
      }
    },
    "rules": {
      "must": [
        "Keep all secrets and credentials only in app.yaml, databricks.yml, and/or .env"
      ],
      "should": [
        "ask user when in doubt of resource to use for plugin"
      ],
      "never": [
        "guess resources when multiple or no options are available",
        "embed secrets in files that will go to the client-bundle"
      ]
    }
  }
}

```

| Field          | Description                                                             |
| -------------- | ----------------------------------------------------------------------- |
| `command`      | Scaffolding command the agent should invoke.                            |
| `flags`        | Map of flag name to `{ description, required?, pattern?, default? }`.   |
| `rules.must`   | Actions the scaffolding agent must always perform.                      |
| `rules.should` | Recommended actions — applied unless overridden by a plugin-level rule. |
| `rules.never`  | Actions the scaffolding agent must never perform.                       |

The example above shows three flags. The canonical flag set shipped with every synced template manifest is:

| Flag             | Required | Description                                                                                                                    |
| ---------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `--name`         | yes      | Project name — sets `{{.projectName}}` in `package.json`, `databricks.yml`, and `.env`.                                        |
| `--template`     | no       | Template path (local directory or GitHub URL).                                                                                 |
| `--version`      | no       | AppKit version to use; defaults to auto-detected.                                                                              |
| `--features`     | no       | Plugins to enable (comma-separated, no spaces; must match keys in the `plugins` map).                                          |
| `--set`          | no       | Set resource values (format: `plugin.resourceKey.field=value`, repeatable).                                                    |
| `--output-dir`   | no       | Directory to write the project to.                                                                                             |
| `--description`  | no       | App description.                                                                                                               |
| `--run`          | no       | Run the app after creation (`none`, `dev`, `dev-remote`).                                                                      |
| `--auto-approve` | no       | Skip prompts for optional resources. Not recommended for agent-driven init — conflicts with the "ask user when in doubt" rule. |
| `--profile`      | no       | Databricks CLI profile to use for authentication (global flag).                                                                |

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.

#### Substitutability gate (template rules)[​](#substitutability-gate-template-rules "Direct link to Substitutability gate (template rules)")

The template-level rules survive the same substitutability gate that governs plugin-level rules: each entry must describe a **cross-cutting agent decision** the schema cannot already encode. Rules like "modify only files inside the template directory" or "list volumes after prompting for catalog/schema" are absent on purpose — the first is unreachable once you read the manifest as the source of truth, and the second is now encoded structurally as `parents: ["catalog", "schema"]` on the `volume` discovery kind (see [Plugin manifest — Transient prompts](./docs/plugins/manifest.md#transient-prompts-parents)).

## See also[​](#see-also "Direct link to See also")

* [Plugin manifest](./docs/plugins/manifest.md) — the authoring side (`manifest.json`).
* [Plugin management](./docs/plugins/plugin-management.md) — `appkit plugin sync`, `appkit plugin create`.
* [Configuration](./docs/configuration.md) — environment variables.
