Template Variables
Create dynamic content in your StructKit configurations with template variables.
Custom Delimiters
StructKit uses custom Jinja2 delimiters to avoid conflicts with YAML:
- Variables:
{{@and@}} - Blocks:
{%@and@%} - Comments:
{#@and@#}
Basic Syntax
files:
- README.md:
content: |
# {{@ project_name @}}
Welcome to {{@ project_name @}}!Block Syntax
files:
- config.yaml:
content: |
{%@ if environment == "production" @%}
debug: false
{%@ else @%}
debug: true
{%@ endif @%}Interactive Variables
Define variables that prompt users for input with descriptions, types, defaults, and validation:
variables:
- project_name:
description: "The name of your project"
type: string
default: "MyProject"
- port:
description: "Application port"
type: integer
default: 8080
- IS_ENABLED:
type: boolean
required: true
- ENV:
type: string
enum: [dev, prod]
- TOKEN:
type: string
env: MY_TOKENSupported types: string, integer, number, boolean. Validation options include required, enum, regex/pattern, min/max, and env/default_from_env.
Built-in Variables
file_name— The name of the file being processedfile_directory— The directory containing the file
Custom Filters & Globals
uuid()
id: {{@ uuid() @}}now()
generated_at: {{@ now() @}}Warning: uuid() and now() are non-deterministic. A file using either produces different content on every run, so it always shows up in structkit generate --dry-run --diff output — which destroys that diff's value as a drift check. Confine them to files marked skip_if_exists: true.
env(name, default="")
TOKEN={{@ env("TOKEN", "changeme") @}}read_file(path)
{{@ read_file("INTRO.md") @}}current_repo()
Returns owner/repo for the git repository in the working directory, read from remote.origin.url. Works with both HTTPS and SSH remotes and returns an error string for non-GitHub remotes. This is what makes README badge URLs work without declaring a variable.
 @}}/actions/workflows/ci.yml/badge.svg)to_yaml / from_yaml / to_json / from_json
to_json accepts an optional indent argument.
{{@ some_dict | to_yaml @}}
{{@ some_dict | to_json @}}
{{@ some_dict | to_json(indent=2) @}}latest_release(repo_name, strip_v=false, return_sha=false)
Resolves the latest GitHub release of a repository. Both options are optional and appended, so existing one-argument calls behave exactly as before.
| Parameter | Type | Default | Description |
|---|---|---|---|
| repo_name | str | required | GitHub repository in owner/name form |
| strip_v | bool | false | In version mode, removes exactly one leading lowercase v from the tag. v1.2.3 → 1.2.3, vv1.2.3 → v1.2.3, V1.2.3 → V1.2.3 |
| return_sha | bool | false | Returns the commit SHA identified by the release tag instead of the tag name |
files:
- release-info.txt:
content: |
Version: {{@ "nodejs/node" | latest_release @}} # v22.0.0
Version without v: {{@ "nodejs/node" | latest_release(strip_v=true) @}} # 22.0.0
Commit: {{@ "nodejs/node" | latest_release(return_sha=true) @}} # full commit SHA- When both
return_sha=trueandstrip_v=trueare set,return_shawins andstrip_vis a no-op (a SHA is never stripped). - SHA resolution: the SHA comes from the Git tag reference. Lightweight tags resolve directly; annotated tags are peeled until a commit is reached. Resolution examines at most 10 Git objects, so no more than nine annotated-tag hops may precede the final commit. The returned object ID must be a full 40-character SHA-1 or 64-character SHA-256 hex value.
- Fallback: if repository lookup succeeds but the latest-release lookup fails, version mode returns the repository's default branch name and SHA mode returns that branch's head commit SHA.
strip_vis never applied to a fallback branch name. - Errors: if repository lookup or fallback resolution fails, the filter returns
LATEST_RELEASE_ERROR. Once a release has been selected, a tag that is missing, malformed, cyclic, too deeply nested, or does not resolve to a commit also returnsLATEST_RELEASE_ERRORrather than silently falling back to an unrelated revision. - Requirements: the filter uses PyGithub — set the
GITHUB_TOKENenvironment variable for private repositories and for authenticated API rate limits.
Handy for pinning Terraform providers:
version = "{{@ "hashicorp/terraform-provider-aws" | latest_release(strip_v=true) @}}"
source_sha = "{{@ "DataDog/terraform-provider-datadog" | latest_release(return_sha=true) @}}"slugify
Takes no arguments. It lowercases the value, converts runs of whitespace to a single hyphen, then removes every character that is not a-z, 0-9, or -. Underscores are removed, not converted — My_Project becomes myproject.
server_name {{@ project_name | slugify @}};For a different separator, chain replace:
src/{{@ project_name | slugify | replace("-", "_") @}}/__init__.pydefault_branch
branches: [ {{@ "httpdss/structkit" | default_branch @}} ]The with Clause
Pass additional variables to nested structures:
folders:
- frontend/:
struct: project/react
with:
app_name: "{{@ project_name @}}-frontend"
port: 3000
- backend/:
struct: project/node
with:
app_name: "{{@ project_name @}}-backend"
port: 8000Advanced Examples
Conditional File Creation
files:
- docker-compose.yml:
skip: "{{@ not use_docker @}}"
content: |
version: '3.8'
services:
app:
image: {{@ project_name | slugify @}}:latestDynamic File Names
files:
- "src/{{@ module_name @}}/index.js":
content: |
// {{@ module_name @}} module
export default {};Environment-Specific Content
files:
- config/{{@ environment @}}.yml:
content: |
{%@ if environment == "production" @%}
database_url: {{@ production_db_url @}}
{%@ else @%}
database_url: sqlite:///dev.db
{%@ endif @%}Related
- YAML Configuration — Full YAML reference: structures, file content, variables, hooks, mappings, and remote sources.
- Examples — Real-world YAML configs for Python apps, monorepos, Docker stacks, and Terraform modules.
- Hooks — Run pre- and post-generation shell commands: git init, install deps, formatters, builds.