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.tsx

The 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:

  1. 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.
  2. 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-shadcn ships 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-template against 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 --json

For programmatic use (build + publish a catalogue page):

import { listTemplates } from '@voltro/cli'
const all = await listTemplates()
// [{ id, kind, summary, tags, path }, …]

See also