Skip to content

Creating Templates

Three files. That’s all you need.

hello/
diecut.toml
template/
hello.txt.tera

The config — diecut.toml:

[template]
name = "hello"
[variables.name]
type = "string"
prompt = "Your name"
default = "world"

The template file — template/hello.txt.tera:

Hello, {{ name }}!

Run it:

Terminal window
diecut new ./hello -o output
Your name [world]: Alice
output/
hello.txt
output/hello.txt
Hello, Alice!

That’s a template. You just made one.

The template/ directory contains your project files. Files ending in .tera are rendered through the Tera template engine, then have the .tera suffix stripped. Everything else is copied as-is.

diecut supports six variable types.

Text input.

[variables.project_name]
type = "string"
prompt = "Project name"
default = "my-project"

Yes or no.

[variables.use_ci]
type = "bool"
prompt = "Set up CI?"
default = true

Integer.

[variables.port]
type = "int"
prompt = "Default port"
default = 8080

Decimal number.

[variables.version]
type = "float"
prompt = "Initial version"
default = 0.1

Pick one from a list.

[variables.license]
type = "select"
prompt = "License"
choices = ["MIT", "Apache-2.0", "GPL-3.0"]
default = "MIT"

Pick multiple from a list.

[variables.features]
type = "multiselect"
prompt = "Features to include"
choices = ["logging", "docker", "ci"]
default = ["logging"]

Use when to only ask a question when a previous answer makes it relevant.

[variables.use_ci]
type = "bool"
prompt = "Set up CI?"
default = true
[variables.ci_provider]
type = "select"
prompt = "CI provider"
choices = ["github-actions", "gitlab-ci"]
when = "{{ use_ci }}"

ci_provider is only prompted when use_ci is true. The when field takes a Tera expression.

Include or exclude entire files based on variable values.

[files]
conditional = [
{ pattern = ".github/**", when = "use_ci and ci_provider == 'github-actions'" },
{ pattern = ".gitlab-ci.yml", when = "use_ci and ci_provider == 'gitlab-ci'" },
]

Files matching the pattern are only included when the expression is true.

A real-world example from a Python template:

[files]
conditional = [
{ pattern = "src/cli.py*", when = "use_cli" },
]

This excludes src/cli.py.tera when the user says no to the CLI entry point. The glob matches both the .tera source and any related files.

Variables derived from other variables. Never prompted.

[variables.project_slug]
type = "string"
computed = "{{ project_name | slugify }}"

Computed variables are available in templates but never shown to the user. Use them for derived values like slugs, lowercase versions, or formatted strings.

A computed variable must not have a prompt field. If it does, diecut will report an error.

Keep files out of the generated output:

[files]
exclude = ["*.pyc", ".DS_Store", "__pycache__/**"]

Some files contain {{ }} or {% %} syntax that isn’t meant for Tera. Binary files, images, or files with their own template syntax.

[files]
copy_without_render = ["assets/**/*.png", "fonts/**"]

Files matching these patterns are copied verbatim. Tera syntax inside them is left alone.

Five things you’ll actually use.

Insert a value:

{{ project_name }}

Include a block only when a condition is true:

{% if use_ci %}
ci: true
{% endif %}

Iterate over a list:

{% for feature in features %}
- {{ feature }}
{% endfor %}

Transform values inline:

{{ project_name | slugify }}
{{ name | upper }}
{{ description | truncate(length=50) }}

slugify turns “My Project” into “my-project”. upper uppercases. truncate cuts to length.

{# This won't appear in output #}

A Cargo.toml.tera from a Rust CLI template:

[package]
name = "{{ project_name }}"
version = "0.1.0"
edition = "{{ rust_edition }}"
description = "{{ description }}"
{% if author %}authors = ["{{ author }}"]
{% endif %}license = "{{ license }}"

Tera has much more. See the full Tera documentation for everything else.

Terminal window
diecut check ./my-template

Reports format detection, variable definitions, and any issues it finds.

Terminal window
diecut ready ./my-template

ready is stricter. It checks whether the template is suitable for sharing: description present, variables have prompts, no orphaned references.

Add a regex to constrain user input:

[variables.project_name]
type = "string"
prompt = "Project name"
validation = '^[a-z][a-z0-9_-]*$'
validation_message = "Must start with a letter. Only lowercase letters, numbers, hyphens, underscores."

If the input doesn’t match, the user is re-prompted with the validation message. No bad data gets through.