---
sidebar_position: 6
---

# 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

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

| 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

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`

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](../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

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](../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

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

Each plugin's `scaffolding.rules` block (see [Plugin manifest — Scaffolding rules](../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)

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)

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](../plugins/manifest.md#transient-prompts-parents)).

## See also

- [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.
