Roll your own
Add a template the CLI can scaffold from — a directory under voltro-templates/apps/ with a manifest and the files to copy.
Templates aren't magic. Each one is a directory under voltro-templates/apps/<id>/ with a template.json manifest plus the files to scaffold. Drop a new directory and the CLI walks it on the next voltro list-templates.
The directory NAME is the template id you pass to --api / --web / --template. The id must match ^[a-zA-Z][a-zA-Z0-9-]*$ — kebab-lowercase, no @ or /.
Minimum viable template
my-template/
├── template.json
├── package.json
├── app.config.ts
├── tsconfig.json
└── src/
└── pages/
└── index.tsxThe template.json manifest (id, kind, summary, optional tags):
{
"id": "acme-internal-tool",
"kind": "web",
"summary": "Internal tool starter — our team's auth + table conventions baked in.",
"tags": ["internal", "tool"]
}kind is 'api' or 'web' — the CLI rejects an api template passed to --web (and vice-versa). id must equal the directory name, or the loader skips the dir.
Now voltro list-templates includes it, and voltro create-project x --web=acme-internal-tool (web) or --api=acme-internal-tool (api) scaffolds it.
Token substitution
Any file's CONTENT can contain placeholders. The scaffolder rewrites them from the app + project names you pass; the kebab form is what's persisted (folders, package.json name), camel/Pascal/snake are derived per use:
| Token | Form | Example (appName=my-app, projectName=acme-cms) |
|---|---|---|
{{appName}} |
kebab | my-app |
{{projectName}} |
kebab | acme-cms |
{{appNameCamel}} |
camelCase | myApp |
{{projectNameCamel}} |
camelCase | acmeCms |
{{appNamePascal}} |
PascalCase | MyApp |
{{projectNamePascal}} |
PascalCase | AcmeCms |
{{capAppName}} |
PascalCase alias of appNamePascal |
MyApp |
{{capProjectName}} |
PascalCase alias of projectNamePascal |
AcmeCms |
{{appNameSnake}} |
snake_case (SQL / bucket names) | my_app |
{{projectNameSnake}} |
snake_case | acme_cms |
{{port}} |
the port allocated from the project's portRange (web apps) |
5191 |
Example app.config.ts:
export default {
type: 'web' as const,
name: '{{capProjectName}}{{capAppName}}',
port: {{port}},
}Substitution is global across all files (content only — file NAMES aren't templated). The scaffolder also rewrites package.json's name field on the fly so the scaffolded app gets a real pnpm-workspace name even if the template forgot to template it.
Where templates live
The CLI loads templates from two places, in order:
- Source mode (dev): walks up from the CLI's own dir looking for a sibling
voltro-templates/apps/. This is the source of truth in this repo. - Bundled fallback (post-publish): a snapshot of
voltro-templates/apps/shipped inside the CLI package.
To add a template for the framework itself, drop a directory under voltro-templates/apps/<id>/. There is no env-var path override and no npm-package fetch — the loader is hardcoded to those two locations.
What's in a template
| File | Purpose |
|---|---|
template.json |
Manifest with id, kind, summary, optional tags. Stripped — never lands in the scaffolded app. |
package.json |
The scaffolded app's deps + scripts. Its name is rewritten on scaffold. |
app.config.ts |
The app's framework config (port, name, plugins). |
tsconfig.json |
TypeScript config. Usually extends a workspace base. |
README.md |
Human-readable description; copied verbatim (with tokens substituted). |
| everything else | Copied into the target app dir with token substitution. |
The scaffolder copies every file except template.json (and obvious build artefacts — node_modules/, dist/, .framework/). No "templating engine" beyond the token-replacement walker.
Tips for good templates
- Start small. A short index page + the right deps beats a 2000-line "everything imaginable".
- Inline comments explain the WHY. Scaffolded code is read by the next dev. The shipped templates' comments are part of the value.
- Use the kit primitives. Don't re-invent a Button when
@voltro/ui-shadcnships one. - Leave room. Show the pattern; don't fill every file with example data. The user will replace it.
- Test by scaffolding. Before relying on it,
voltro create-project --web=my-templateagainst a clean directory. Boot it. Refine.
The framework's own templates
Read voltro-templates/apps/*/ for reference. Every shipped template's source is the best example of "what a good template looks like" — short, opinionated, idiomatic Voltro.
Listing templates
voltro list-templates
voltro list-templates --jsonFor programmatic use (build + publish a catalogue page):
import { listTemplates } from '@voltro/cli'
const all = await listTemplates()
// [{ id, kind, summary, tags, path }, …]See also
- Scaffolding CLI — the consumer of templates.
- App templates overview — the shipped catalogue.