Templates
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:
- The
.tmplsuffix is stripped (e.g..env.tmpl→.env) - Go template expressions are evaluated and substituted
- 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:
{{- 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)
.dotEnvpopulation — resource fields with anenvproperty are written to.envapp.yamlgeneration — resource fields produceenv+valueFromentriesdatabricks.ymlgeneration — 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 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. Addsscaffolding(required by the CLI whenversionis"2.0"; enforced at parse time, not via the published JSON Schema) and theoriginfield on every resource field entry. JSON Schema published athttps://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). |
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:
{
"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) 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:
- Gather rules from every selected plugin and from every plugin with
requiredByTemplate: true. - Apply the template-level
scaffolding.ruleson top. - Plugin-level rules override skill-baked or template-level defaults at the same directive site.
- A plugin
mustthat conflicts with a templatenever(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.
{
"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).
See also
- Plugin manifest — the authoring side (
manifest.json). - Plugin management —
appkit plugin sync,appkit plugin create. - Configuration — environment variables.