---
title: Writing templates
description: The version 2 blueprint format, with metadata, inputs, generated secrets and volumes, and how to check a template before you use it.
section: Templates
order: 2
updated: 2026-11-04
---

Templates are [dekka.yaml blueprints](/docs/config-as-code#describe-a-whole-environment-in-dekkayaml) with `version: 2`, which adds a few keys to the version 1 format: `metadata` for how the template is shown, `inputs` for values the person deploying provides, expressions that generate secrets, and a `volume` on a service. Everything from version 1, including resource specs, shared variables, references and connections, works the same way, and you can use version 2 in your own dekka.yaml to plan an environment that needs inputs or volumes.

## A complete example

```yaml title="dekka.yaml"
version: 2
name: Notes
description: A notes app with Postgres, an admin token and a disk for uploads.
metadata:
  category: frameworks
  summary: A notes app with its database and uploads.
  tags: [notes, postgres]
  maintainer: community
  notes: Sign in with the admin token, which you can reveal from the service's variables.
inputs:
  - key: SMTP_PASSWORD
    label: SMTP password
    description: For sending sign-in emails.
    secret: true
    required: true
  - key: REGION_LABEL
    label: Region label
    default: accra
variables:
  # Shared, so it's generated once and kept on every later plan.
  SESSION_KEY: ${{secret(32)}}
resources:
  - name: notes
    kind: application
    spec:
      source: { provider: image, image: "ghcr.io/acme/notes:1.4" }
      runtime: { port: 8080, health_check_path: /health, memory_mib: 512 }
    volume: { mount_path: /data, size_gb: 1 }
    variables:
      SESSION_KEY: ${{shared.SESSION_KEY}}
      ADMIN_TOKEN: adm_${{secret_hex(16)}}
      SMTP_PASSWORD: ${{input.SMTP_PASSWORD}}
      REGION_LABEL: ${{input.REGION_LABEL}}
      PUBLIC_URL: ${{notes.DEKKA_PUBLIC_URL}}
      DB_HOST: ${{notes.PGHOST}}
  - name: notes-db
    kind: postgres
    spec: { version: "17", storage_mib: 512 }
connections:
  - { from: notes, to: notes-db }
```

## Metadata

`metadata` decides how the template appears in the library, and plain blueprints leave it out.

| Key | What it holds |
| --- | --- |
| `category` | Required. One of `ai`, `automation`, `analytics`, `monitoring`, `databases`, `frameworks`, `search`, `storage` or `cms`. |
| `summary` | Required. One line for the template's card, up to 140 characters. |
| `logo` | An icon name in lowercase letters, numbers and hyphens, such as `n8n`. |
| `tags` | Up to 8 words that help search find it. |
| `website`, `source_url` | Links to the project's site and its source code. |
| `license` | The upstream licence, such as `MIT` or `AGPL-3.0`. |
| `maintainer` | `dekka` or `community`, which is `dekka` unless you set it. |
| `docs_slug` | A docs page about the template. |
| `notes` | What to do after deploying, such as creating the first account, up to 600 characters. |
| `featured` | `true` to list the template first. |

## Inputs

Each entry in `inputs` is a value asked for when the template is planned, and a variable uses it as `${{input.KEY}}`. A template can declare up to 20.

| Key | What it does |
| --- | --- |
| `key` | Required. The name used in `${{input.KEY}}`, in letters, numbers and underscores. |
| `label` | Required. The field's label in the dashboard. |
| `description` | A line under the field explaining what the value is for. |
| `secret` | `true` to mask the field and store every variable that uses it as a secret. |
| `required` | `true` to refuse a plan without it. |
| `default` | The value used when none is given. |
| `placeholder` | Example text shown in the empty field. |

A required input left empty answers 422 `missing_input`, whose `details.inputs` lists each key. When an optional input is left empty and a variable's value is nothing but that input, the variable isn't set at all.

## Generated secrets

`${{secret(N)}}` becomes N random bytes written as URL-safe base64, and `${{secret_hex(N)}}` becomes N random bytes as hexadecimal, so `secret_hex(16)` gives 32 characters. N is between 8 and 128. Expressions can sit inside a longer value, as in `sk-${{secret(24)}}`, and each one gets its own value.

Dekka replaces expressions when the blueprint is planned, and any variable that used a secret expression or a secret input is stored as a secret and never shown in the plan. A shared variable that already exists keeps its value on every later plan, so put a generated key that protects stored data, such as an encryption key, in `variables` at the top level and reference it with `${{shared.KEY}}`. A service variable with a secret expression gets a new value each time you plan the blueprint again.

## Volumes

`volume` on a service creates a disk with it, mounted at `mount_path`, with `size_gb` between 1 and 1024 that defaults to 1. A service has at most one volume and runs a single replica, and databases manage their own storage, so they can't have one. The disk is named after the service, such as `notes-data`, and works like any other [volume](/docs/volumes).

## Variables a template can reference

References such as `${{api.DEKKA_PRIVATE_DOMAIN}}` and `${{shared.KEY}}` stay in the stored value and resolve on every deploy, as described in [Variables](/docs/environment-variables#reference-another-services-variable). Templates often use these as well.

- `DEKKA_PUBLIC_URL` is a web service's public address with `https://`, which suits settings such as a webhook URL.
- A connection to PostgreSQL also gives the service `PGHOST`, `PGPORT`, `PGDATABASE`, `PGUSER` and `PGPASSWORD`, for software that wants the parts of `DATABASE_URL` separately.
- A connection to Redis also gives `REDIS_HOST`, `REDIS_PORT` and `REDIS_PASSWORD` next to `REDIS_URL`.

A service can reference its own connection variables, as `${{notes.PGHOST}}` does above, and the passwords stay secret.

## Check a template

`POST /v1/blueprints/validate` checks a blueprint without an environment and lists every problem with its path.

```bash tab="cURL"
curl https://api.dekka.cloud/v1/blueprints/validate \
  -H "Authorization: Bearer $DEKKA_API_KEY" \
  --json "$(jq -Rs '{ content: . }' dekka.yaml)"
```

These are the errors specific to version 2.

| Path | Message |
| --- | --- |
| `inputs` | each input needs its own key |
| `resources.0.variables.KEY` | `${{input.NAME}}` names an input the template doesn't declare |
| `resources.0.variables.KEY` | `${{secret(4)}}` must generate between 8 and 128 bytes |
| `resources.1.volume` | only services have volumes; databases manage their own storage |
| `resources.0.volume` | a service with a volume runs one replica |

To try a template in an environment, plan it with its inputs and read the changes before deploying. Nothing is created until you deploy the changeset.

```bash tab="cURL"
curl https://api.dekka.cloud/v1/environments/$ENVIRONMENT_ID/blueprint/plan \
  -H "Authorization: Bearer $DEKKA_API_KEY" \
  --json "$(jq -Rs '{ content: ., inputs: { SMTP_PASSWORD: "…" } }' dekka.yaml)"
```
