Template Authoring Guide

How to build your own fledge templates.

Overview

A template is a directory with a template.toml manifest and whatever files you want. Files get rendered through Tera (Jinja2-style) before being written to the output.

Directory Structure

my-template/
├── template.toml          # manifest (required)
├── src/
│   └── main.rs            # template files, Tera syntax works here
├── README.md
├── Cargo.toml
└── .github/
    └── workflows/
        └── ci.yml

template.toml Reference

This is where you define metadata, prompts, file rules, and hooks.

Basic Structure

[template]
name = "my-template"
description = "A short description"
min_fledge_version = "1.0.0"          # optional

[prompts]
description = { message = "Project description", default = "A new project" }
port = { message = "Default port", default = "3000" }

# Defaults can reference earlier variables:
repo_url = { message = "Repository URL", default = "https://github.com/{{ github_org }}/{{ project_name }}" }

[files]
render = ["**/*.rs", "**/*.toml", "**/*.md", "**/*.yml"]
copy = ["**/*.png", "**/*.ico"]
ignore = ["template.toml"]

[hooks]
post_create = ["cargo fmt", "npm install"]

[template] section

KeyTypeRequiredNotes
namestringYesWhat you pass to --template
descriptionstringYesShows up in fledge templates list
versionstringNoTemplate version (informational, shown in init summary)
min_fledge_versionstringNoMinimum fledge version needed (checked before rendering)
requiresstring[]NoTools that must be on PATH (e.g. ["node", "npm"])

[prompts] section

Each key becomes a template variable that gets prompted to the user.

[prompts]
# With a default
description = { message = "Project description", default = "A new project" }

# No default, user has to answer
main_author = { message = "Primary author" }

# Default can reference other variables
repo_url = { message = "Repository URL", default = "https://github.com/{{ github_org }}/{{ project_name }}" }

[files] section

Controls which files get rendered, copied, or skipped.

  • render - process through Tera
  • copy - copy as-is, even if a render glob would otherwise match (use this for binary assets, images, fonts, anything you don’t want Tera to touch)
  • ignore - skip entirely (file is not written to the project)

Files ending in .tera are always rendered (and the extension stripped) regardless of the globs. That is the explicit “render this” signal.

Precedence. First rule that matches wins:

  1. ignore glob → skip
  2. .tera extension → render, strip extension
  3. copy glob → copy bytes verbatim
  4. render glob → render through Tera
  5. Default (nothing matched) → copy bytes

So a broad render = ["**/*"] is safe as long as binary assets are listed under copy. They bypass Tera even though the render glob would catch them.

[files]
render = ["**/*.rs", "**/*.toml", "**/*.md", "**/*.yml"]
copy = ["**/*.png", "**/*.ico", "assets/**"]
ignore = ["template.toml", "node_modules/**"]

[hooks] section

Commands that run after scaffolding, inside the new project directory.

[hooks]
post_create = ["cargo fmt", "cargo test", "git add -A && git commit -m 'Initial commit'"]

Built-in templates run hooks automatically. Remote templates show the commands and ask for confirmation (unless you pass --yes).

Built-in Variables

These are always available. You don’t need to define them in [prompts]:

VariableWhat it isExample
project_nameName as the user typed itmy-cool-app
project_name_snakeSnake casemy_cool_app
project_name_pascalPascalCaseMyCoolApp
authorFrom config, git, or promptedLeif
github_orgFrom config or promptedCorvidLabs
licenseFrom config, defaults to MITMIT
yearCurrent year2026
dateCurrent date2026-04-18

Tera Syntax

Templates use Tera syntax. Here’s the stuff you’ll actually use:

Variables

# {{ project_name }}

{{ description }}

Conditionals

{% if license == "MIT" %}
This project is MIT licensed.
{% endif %}

Loops

{% for dep in dependencies %}
- {{ dep }}
{% endfor %}

Filters

Project slug: {{ project_name | slugify }}
Uppercase: {{ author | upper }}

Putting it together

# {{ project_name }}

{{ description }}

## Author

Created by {{ author }} ({{ github_org }}) in {{ year }}.

{% if license == "MIT" %}
This project is MIT licensed.
{% endif %}

## Quick Start

```
cd {{ project_name_snake }}
cargo build
```

Quick Start with fledge templates create

The fastest way to start a new template:

fledge templates create my-template
fledge templates create my-template --hooks --prompts --yes

This scaffolds a ready-to-edit template directory with template.toml and example files. See the CLI reference for all options.

Building a Template from Scratch

1. Make the directory

mkdir python-api && cd python-api

2. Write template.toml

[template]
name = "python-api"
description = "Python FastAPI project with Docker"

[prompts]
description = { message = "Project description", default = "A FastAPI application" }
python_version = { message = "Python version", default = "3.12" }

[files]
render = ["**/*.py", "**/*.toml", "**/*.md", "**/*.yml", "Dockerfile"]
ignore = ["template.toml"]

[hooks]
post_create = ["python -m venv .venv"]

3. Add your files

# app/main.py
"""{{ description }}"""
from fastapi import FastAPI

app = FastAPI(title="{{ project_name_pascal }}")

@app.get("/")
def root():
    return {"name": "{{ project_name }}"}
# Dockerfile
FROM python:{{ python_version }}-slim
WORKDIR /app
COPY . .
RUN pip install -e .
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0"]

4. Test it

fledge templates init test-api --template ./python-api --dry-run
fledge templates init test-api --template ./python-api

Testing

Add your template directory to config:

# ~/.config/fledge/config.toml
[templates]
paths = ["~/dev/my-templates"]

Or point at it directly:

fledge templates init test-project --template ./my-template

The loop is: edit files → fledge templates init test-output --template my-template → check the output → delete test output → repeat.

Sharing

GitHub

Push your template to a GitHub repo. Anyone can use it with:

fledge templates init my-app --template user/my-template

Template Repos

Users can register your repo so it shows up in fledge templates list:

[templates]
repos = ["CorvidLabs/fledge-templates", "myorg/templates"]

Tips

  • Name it clearly. python-api beats template-1.
  • Write a README. Explain what the template does and what variables it uses.
  • Default everything you can. Only prompt for things that actually vary.
  • Test your hooks. Make sure post_create commands handle missing tools gracefully.
  • Use min_fledge_version if you depend on newer features.