Skip to main content

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:

  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

VariableDescription
.projectNameProject name from --name or interactive prompt
.workspaceHostDatabricks workspace URL
.profileDatabricks CLI profile name (empty if using host-based auth)
.appDescriptionApp description
.plugins.<name>Non-nil for each selected plugin, enabling conditionals
.dotEnv.contentGenerated .env content from plugin resources
.dotEnv.exampleGenerated .env.example content with placeholders
.bundle.*Generated databricks.yml sections (variables, resources, target variables)
.appEnvGenerated 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)
  • .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 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:

PropertyDescription
envEnvironment variable name written to .env and app.yaml
descriptionShown in the interactive prompt and bundle variable description
localOnlyOnly written to .env for local dev; excluded from app.yaml and bundle variables
bundleIgnoreExcluded from databricks.yml variables (but still in .env)
valueDefault value used when no user input is provided
resolveAuto-populated by CLI from API calls instead of prompting (see below)
examplesExample values shown in field descriptions
discoveryHow the CLI lists candidate values for the field (see Plugin manifest — Resource discovery).
originv2.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:

OriginTriggerMeaning
"platform"localOnly: trueAuto-injected by Databricks Apps at deploy time. Generated for local .env only; absent from app.yaml and bundle variables.
"static"value setHardcoded literal. CLI does not prompt.
"cli"resolve setResolved by the CLI from API calls (e.g. postgres:host).
"user"none of the aboveUser 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 keyDescription
postgres:hostPostgres host from the branch's read-write endpoint
postgres:databaseNamePostgres database name from the database resource
postgres:endpointPathLakebase 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:

  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.

{
"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"
]
}
}
}
FieldDescription
commandScaffolding command the agent should invoke.
flagsMap of flag name to { description, required?, pattern?, default? }.
rules.mustActions the scaffolding agent must always perform.
rules.shouldRecommended actions — applied unless overridden by a plugin-level rule.
rules.neverActions the scaffolding agent must never perform.

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

FlagRequiredDescription
--nameyesProject name — sets {{.projectName}} in package.json, databricks.yml, and .env.
--templatenoTemplate path (local directory or GitHub URL).
--versionnoAppKit version to use; defaults to auto-detected.
--featuresnoPlugins to enable (comma-separated, no spaces; must match keys in the plugins map).
--setnoSet resource values (format: plugin.resourceKey.field=value, repeatable).
--output-dirnoDirectory to write the project to.
--descriptionnoApp description.
--runnoRun the app after creation (none, dev, dev-remote).
--auto-approvenoSkip prompts for optional resources. Not recommended for agent-driven init — conflicts with the "ask user when in doubt" rule.
--profilenoDatabricks 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

Databricks Developer Hub

Ready to ship your next agentic app in minutes?

Read docs