Command Palette

Search for a command to run...

Templates / Creating Templates

Build your own template

A template is a folder of project files plus one required manifest: anesis.template.json. Files ending in .tera are rendered through Tera with the project name and any declared inputs substituted in at generation time. Everything else is copied exactly as-is.

anesis.template.json manifestTera template renderingDeclared inputs + exclude blocksproject_name + input case variables

Folder structure

Your template is a regular directory. The only required file is the manifest at the root — everything else is up to you.

my-template/
├── anesis.template.json   ← required manifest
├── README.md.tera        ← rendered at generation time
├── package.json.tera     ← rendered at generation time
├── src/
│   ├── main.ts.tera
│   └── index.html
└── .gitignore            ← copied as-is (no .tera suffix)

The two kinds of files

Anesis decides at generation time whether to render or copy each file.

*.tera files

Rendered through the Tera template engine. The .tera suffix is removed from the output filename. Use these for any file that should contain the project name.

All other files

Copied exactly as-is — the content is not modified. Use these for binary files, lockfiles, or any file that should never change between projects.

The anesis.template.json manifest

This file must be at the root of your template directory. The CLI reads it to populate the cache index and display metadata in the registry.

{
  "name": "react-vite-ts",
  "version": "0.7.4",
  "anesisVersion": ">=0.7.0",
  "repository": {
    "url": "https://github.com/owner/repo/tree/main/templates/react-vite-ts"
  },
  "metadata": {
    "displayName": "React Vite TypeScript",
    "description": "Starter for React + Vite + TypeScript."
  },
  "inputs": [
    {
      "name": "package_manager",
      "type": "select",
      "description": "Which package manager to target",
      "default": "npm",
      "required": true,
      "options": ["npm", "pnpm", "bun"]
    },
    {
      "name": "use_tailwind",
      "type": "boolean",
      "description": "Include Tailwind CSS setup?",
      "default": false
    }
  ],
  "exclude": [
    { "when": "!use_tailwind", "paths": ["tailwind.config.ts", "src/styles/tailwind.css"] }
  ]
}
name
The template's identifier used in CLI commands. Must use only letters, numbers, hyphens, and underscores.
version
A semantic version string for the template itself.
anesisVersion
A semver range describing the minimum Anesis CLI version required. Use ">=0.7.0" to require at least that version.
repository.url
The GitHub URL where this template lives. Can be a repo root or a subdirectory path — this is the same URL passed to anesis template publish.
metadata.displayName
The human-readable name shown in the template registry UI.
metadata.description
A short sentence describing what this template creates.
inputs
Optional array of input definitions — same shape as addon inputs (text, boolean, select). Anesis prompts for each one before generating any files, and every value is available to .tera files.
exclude
Optional array of { "when": "<input-expr>", "paths": [...] } blocks. Paths listed are skipped at generation time when the expression is truthy. Prefix an input name with "!" to negate it, e.g. "!use_tailwind".

Available template variables

The project name is always injected, and every declared input adds itself plus four derived casing forms.

# Always available in every .tera file:
{{ project_name }}         # exactly what the user typed: "my-app"
{{ project_name_kebab }}   # lowercase with dashes: "my-app"
{{ project_name_snake }}   # lowercase with underscores: "my_app"

# Plus every declared input, and four derived case forms per input.
# Given an input "package_manager" with value "npm":
{{ package_manager }}         → npm
{{ package_manager_pascal }}  → Npm
{{ package_manager_camel }}   → npm
{{ package_manager_kebab }}   → npm
{{ package_manager_snake }}   → npm

project_name_kebab and project_name_snake are derived automatically, and the same happens for every input you declare — you don't need to do any string manipulation in your templates.

Variable usage in .tera files

Use the Tera double-brace syntax anywhere in a .tera file. The output filename has the .tera suffix removed.

// package.json.tera
{
  "name": "{{ project_name_kebab }}",
  "packageManager": "{{ package_manager }}",
  "version": "0.1.0"
}

// src/main.ts.tera
// {{ project_name }} — generated by Anesis
export const appName = "{{ project_name_snake }}";

// README.md.tera
# {{ project_name }}

This project was generated from the react-vite-ts template.

File processing rules and safety

These rules are enforced by the CLI during extraction and rendering.

  • Files ending in .tera are rendered through the Tera template engine. The .tera suffix is stripped from the output filename — package.json.tera becomes package.json.
  • Files without a .tera suffix are copied exactly as-is, with no rendering.
  • Any path matched by an exclude block whose when expression is true at generation time is skipped entirely — it is never written or rendered.
  • Path traversal is blocked at extraction time. A template cannot write files outside the target project directory, regardless of how paths are constructed — this is enforced independently of anything the manifest declares.
  • If a target file already exists on disk (e.g. scaffolding into a non-empty directory with .), Anesis overwrites it and warns you afterwards which paths were overwritten.

Testing your template locally

You don't need to publish to test.

Local test loop

Validate and cache your directory with anesis template link ./my-template, then run anesis new test-project my-template to verify the output. Check that .tera files render correctly, that inputs and their derived case forms come through, that exclude blocks skip the right paths, and that all plain files are copied intact.

When it looks right, continue to Publishing your template →