Scaffolding

init, create-project, add-app, list-templates — boot new code with the framework's conventions baked in.

The scaffolder generates new projects + new apps from templates. Each template is a dogfooded reference; what you scaffold is the same shape the Voltro Cloud team uses.

voltro init

voltro init          # takes no arguments — it initialises the CURRENT directory

Turns the directory you are standing in into a Voltro workspace root, and scaffolds no apps at all. That is the whole distinction from create-project: init prepares the root, create-project fills it. They share one implementation (ensureWorkspaceRoot), so a greenfield create-project needs no separate init — it bootstraps the same root when there isn't one.

What it writes, all of it idempotent and additive:

File Behaviour
pnpm-workspace.yaml Written only when the walk up finds no workspace at all.
package.json (root) Created when missing — private, type: 'module', node/pnpm engines, the detected packageManager, the four scripts, typescript + @types/node. When it exists, only the missing keys are filled in; a script or a version range you already declared is never rewritten.
tsconfig.base.json Written when missing. Every app + package tsconfig the framework generates extends this exact path, so tsc fails before it reads your code without it.
.gitignore Created when missing; otherwise only the entries it does not already cover are appended. Includes .env.local — where voltro dev mints per-project secrets, and which must never be committed.
git init Only when nothing at or above the directory is already a git working tree.

The four root scripts are the workspace fan-outs:

{ "dev": "pnpm -r --parallel dev", "build": "pnpm -r build",
  "test": "pnpm -r test", "typecheck": "pnpm -r typecheck" }

Two refusals, both deliberate:

  • A positional argument is an error (init takes no arguments — it initialises the current directory), with a hint pointing at voltro create-project <name>. voltro init acme reads like "make me a project called acme", and it is not that command.
  • It will not nest a second root inside an existing pnpm workspace. If an ancestor already has a pnpm-workspace.yaml, it names that root and tells you to run create-project from there instead.

A second run on an already-initialised root prints is already a Voltro workspace root — nothing to do.

create-project

voltro create-project <name> [flags]

Bootstraps a new project under apps/<name>/ with selected templates.

Flag Default Notes
--api <templateId> prompts, then api-backend API template. Use none for web-only projects. Also accepts --api=<id>.
--web <templateId> prompts, then frontend-blank Web template. Use none for api-only. Also accepts --web=<id>.
--cache=redis off Wire the Redis cache backend at scaffold time — sets cache: 'redis' in app.config.ts and injects the redis service + CACHE_* env into the active baseline's infra.
--baseline=<bare|compose|helm> prompts (or skip) Deploy baseline to scaffold (bare / compose / helm). Without it, the interactive prompt lists the available ids.
--port-range <start>-<end> 5190-5199 Port range for web apps in this project. Persisted in project.json. Also accepts : / .. separators.
--no-input false Skip prompts; suitable for CI / scripted scaffolding.
--no-register false Do not contact the Voltro Cloud control plane. See registration below.

What it does:

  1. Validates the name (camelCase or kebab-case, no _, no leading digits).
  2. Picks the next free port from --port-range.
  3. Renders templates into apps/<name>/api/ + apps/<name>/web/, substituting {{appName}}, {{projectName}}, {{port}} placeholders.
  4. Writes apps/<name>/project.json recording the project's port range + app list.
  5. Updates pnpm-workspace.yaml to include the new project's apps.
  6. Seeds the agent guide for each app (see below).

After scaffolding:

cd <repo-root>
pnpm install
pnpm dev

Project registration

create-project and add-app finish by registering the project with the Voltro Cloud control plane. This is how self-hosted use is counted, and the Terms of Service ask for projects and apps to be registered. It is worth knowing exactly when it happens and what it involves, because it is part of your first command.

If you are not logged in, nothing is sent. No network call is attempted at all, and the scaffolder says so:

→ cloud registration: skipped — not logged in, so nothing was sent from this machine.
  Registration is how SELF-HOSTED use is counted; the Voltro Cloud Terms of Service ask
  for projects and apps to be registered once you have an account.
  Register later:   voltro cloud login   then   voltro cloud scan
  Never ask again:  scaffold with --no-register

If you are logged in, it prints the destination and the contents before the call:

→ registering project 'acme' with https://cloud.voltro.dev (self-hosted usage tracking, ToS-governed)
  Sends: the project slug, and per app its name, kind, framework version and the NAMES of
         declared primitives (queries, mutations, tables, plugins, …) plus a page count.
  Never sends: source code, row data, environment values or secrets.
  Skip with --no-register.

Pass --no-register to skip it entirely — appropriate for offline work, CI, or while evaluating. You can register later with voltro cloud login followed by voltro cloud scan.

This is the only network call the CLI makes on its own behalf; voltro telemetry reports the rest of the picture (the framework collects nothing).

The seeded agent guide

create-project / add-app (and voltro dev on first boot) seed a guide that teaches AI coding agents the framework's conventions. It's generated, not a monolith:

  • Root AGENTS.md + CLAUDE.md — a slim always-loaded core (mental model, the primitive rubric, file conventions, the browser/server boundary, naming, anti-patterns) plus an index listing the workspace's installed plugins and linking the deep, on-demand topic docs.
  • Nested AGENTS.md + CLAUDE.md per app area (api/, api/database/, web/) — type-specific notes an agent loads only when working there; a scaffolded app's file points at its template's doc.
  • .claude/skills/* — Claude Code skills for the common how-tos.

Existing files are never overwritten; voltro agents-md --force refreshes them. Protect a hand-maintained file from --force with a voltro:agents-md:keep HTML comment at the top.

add-app

voltro add-app <appName> --template <templateId> [--to <projectName>]

Adds another app to an existing project. Works for any kind — api, web, or serverless. A web app gets the next free port from the project's range; a serverless app has no server, so it gets no port (run it with voltro serverless).

Flag Default Notes
--template <templateId> (required) Template to scaffold. voltro list-templates for the catalogue.
--to <projectName> auto-detected Target project. Required when >1 project exists.

What it does:

  1. Reads the target project's project.json for the port range.
  2. Picks the next free port from the range (rejects if the range is exhausted).
  3. Renders the template into apps/<project>/<appName>/.
  4. Updates project.json to record the new app.

Example:

voltro add-app docs --template frontend-docs --to acme
voltro add-app admin --template frontend-blank --to acme

list-templates

voltro list-templates

Prints the catalogue. Templates come in three kindsapi, web, and serverless (a bundle of *.serverless.ts functions deployed on their own):

id                    kind        summary
--------------------  ----------  --------------------------------------------
api-backend           api         Minimal Voltro backend (schema + query + mutation).
api-backend-mail      api         + @voltro/plugin-mail + a React-Email template.
api-backend-storage   api         + @voltro/plugin-storage (public + private objects).
api-backend-mariadb   api         MariaDB binlog CDC + storage, tenant-aware.
api-durable           api         Durable + reactive: workflow, trigger, cron, subscriber, aggregate.
api-ai                api         RAG agent: vectorEmbedding + search tool + model loop.
api-data-advanced     api         Advanced schema: relations, FTS, dbEnum, encrypted, caching.
api-auth              api         Real user auth: plugin-auth sessions + cookie strategy.
api-rest              api         Public REST API: defineRestRoute + plugin-openapi (Swagger).
api-saas              api         SaaS bundle: billing + notifications + analytics + presence.
api-observability     api         Metrics + errors + tracing + a @voltro/testing unit test.
api-webhooks          api         First-class webhooks: signature-verified incoming + outgoing emit.
frontend-blank        web         Empty React + layout shell.
frontend-app          web         Fullstack reactive loop — wired to an api (useSubscription + useMutation).
frontend-landing      web         Static marketing page — zero JS on the wire.
frontend-static-blog  web         SSG blog: getStaticPaths + islands + per-post meta.
frontend-spa          web         Pure client-rendered SPA (no backend, no SSR).
frontend-ssr          web         Server-rendered pages: ssr + isr (revalidate, swr).
frontend-contact      web         Static page + a serverless email form.
frontend-docs         web         Catch-all docs site with i18n.
changelog             web         Release-notes site (MDX + RSS).
edge-functions        serverless  A library of *.serverless.ts functions (8 types).

See the App templates catalogue for what each demonstrates + a picks-for table. For programmatic use, add --json:

voltro list-templates --json

generate (AI app-builder)

voltro generate "<prompt>" turns a natural-language prompt into framework artifacts — queries, mutations, tables — constrained to what your app can actually express. It reads the committed capability manifest (app.manifest.generated.json, emitted by voltro dev) as the grammar, asks the model for an app graph + the files that realise it, and gates every candidate through the real voltro check before anything is written. A structurally-invalid proposal is re-prompted with its diagnostics (the errors-as-LLM-API loop), never written.

voltro generate "add a comments table with a list + create"          # dry-run: prints the proposal
voltro generate "add a comments table with a list + create" --write  # applies the accepted artifacts

Dry-run by default — artifacts hit disk only with --write, and only ever a proposal that passed voltro check. Generation is also capped (file count + total bytes). Run voltro dev once first so the manifest exists.

Requires a model provider (AI_PROVIDER / AI_MODEL + the provider key, same as agents). The cloud dashboard exposes the same builder for proposal review (apps.generateAppGraph), behind the aiBuilder flag (off by default → typed FlagDisabled).

Template tokens

Templates contain {{token}} placeholders that the scaffolder substitutes:

Token Example value
{{appName}} dashboard
{{capAppName}} Dashboard
{{projectName}} acme
{{capProjectName}} Acme
{{port}} 5191

Apply across both file content AND file/directory names — a template file named {{appName}}.entity.ts or a dir pages/{{appNameSnake}}/ renders to your project's names. When writing your own template, use these freely; the substitution is global.

Writing a custom template

Drop a directory into voltro-templates/apps/<your-id>/:

voltro-templates/apps/my-template/
├── template.json
├── package.json
├── app.config.ts
├── src/
│   ├── pages/
│   │   └── index.tsx
│   └── …
└── tsconfig.json

template.json declares the metadata:

{
  "id":      "my-template",
  "kind":    "web",
  "summary": "My custom template — does X.",
  "tags":    ["marketing", "minimal"]
}

kind is one of api / web / serverless. A serverless template ships a functions/ dir of *.serverless.ts files (no app.config.ts, no pages) and is added with voltro add-app. Now voltro list-templates shows it; voltro create-project --web my-template (or --api) uses it, and any kind is added with voltro add-app <name> --template my-template.

For private templates (in your own repo), set VOLTRO_TEMPLATES_DIR=/path/to/my/templates and the CLI resolves templates from there instead of the default location. Point it at the templates root — the CLI appends apps/, so your templates live at /path/to/my/templates/apps/<template-id>/. When the variable is set it is authoritative: the CLI does not fall back to the bundled templates.

Idempotency

The scaffolder refuses to overwrite an existing directory:

voltro create-project acme   # fails if apps/acme/ exists

To force, delete the directory first. The framework intentionally doesn't have a --force flag — accidental data loss is the kind of thing that happens once + ruins your day.

Anti-patterns

  • Scaffolding into a non-Voltro repo. The scaffolder writes to apps/<project>/ + expects a pnpm-workspace.yaml. Without one, pnpm install doesn't link workspace packages.
  • Renaming the scaffolded app directory afterwards. project.json records the path; rename invalidates discovery. Either re-scaffold with the right name, or update project.json + every cross-package import by hand.
  • Editing the templates dir directly to "fix" a scaffolded app. Templates are starting points. After scaffolding, the app is yours — edit IT, not the template (unless the template itself has a bug).

See also

  • App templates — the catalogue with picks-for table
  • Dev — what voltro dev does with the scaffolded app