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 directoryTurns 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 atvoltro create-project <name>.voltro init acmereads 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 runcreate-projectfrom 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:
- Validates the name (camelCase or kebab-case, no
_, no leading digits). - Picks the next free port from
--port-range. - Renders templates into
apps/<name>/api/+apps/<name>/web/, substituting{{appName}},{{projectName}},{{port}}placeholders. - Writes
apps/<name>/project.jsonrecording the project's port range + app list. - Updates
pnpm-workspace.yamlto include the new project's apps. - Seeds the agent guide for each app (see below).
After scaffolding:
cd <repo-root>
pnpm install
pnpm devProject 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-registerIf 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.mdper 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:
- Reads the target project's
project.jsonfor the port range. - Picks the next free port from the range (rejects if the range is exhausted).
- Renders the template into
apps/<project>/<appName>/. - Updates
project.jsonto record the new app.
Example:
voltro add-app docs --template frontend-docs --to acme
voltro add-app admin --template frontend-blank --to acmelist-templates
voltro list-templatesPrints the catalogue. Templates come in three kinds — api, 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 --jsongenerate (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 artifactsDry-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 theaiBuilderflag (off by default → typedFlagDisabled).
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/ existsTo 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 apnpm-workspace.yaml. Without one,pnpm installdoesn't link workspace packages. - Renaming the scaffolded app directory afterwards.
project.jsonrecords the path; rename invalidates discovery. Either re-scaffold with the right name, or updateproject.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 devdoes with the scaffolded app