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.
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)Anesis decides at generation time whether to render or copy each file.
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.
Copied exactly as-is — the content is not modified. Use these for binary files, lockfiles, or any file that should never change between projects.
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"] }
]
}nameversionanesisVersionrepository.urlanesis template publish.metadata.displayNamemetadata.descriptioninputsexcludeThe 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 }} → npmproject_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.
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.These rules are enforced by the CLI during extraction and rendering.
.tera are rendered through the Tera template engine. The .tera suffix is stripped from the output filename — package.json.tera becomes package.json..tera suffix are copied exactly as-is, with no rendering.exclude block whose when expression is true at generation time is skipped entirely — it is never written or rendered..), Anesis overwrites it and warns you afterwards which paths were overwritten.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 →