Wizard inputs
The configuration the tenant fills in at deploy time — declared in your template manifest, surfaced as env vars at boot.
userInputs[] declares the configuration the tenant sees and fills in when they deploy your tool. The deploy wizard renders one field per entry, validates the values, and writes them into the deployment's environment as static env vars before your container boots.
Use it for anything tenant-specific and stable: a default greeting, a company name, an admin email, a feature flag. Don't use it for secrets that rotate frequently or for provider-side credentials — those flow through integrations.
userInputs and wizardLayout are not vendo.yaml fields. They live in your tool's template manifest (templates/<slug>/<version>.json in vendo-templates). The vendo.yaml schema rejects them. The page below documents the shape the deploy worker validates; for the full template manifest reference see Deploy & publish → Manifest.
Shape
In your template manifest:
{
"userInputs": [
{
"key": "GREETING",
"label": "Greeting message",
"description": "Shown on the welcome screen.",
"inputType": "text",
"default": "Welcome",
"category": "user_optional"
},
{
"key": "ADMIN_EMAIL",
"label": "Admin email",
"description": "Used to create the initial admin account.",
"inputType": "text",
"category": "user_required"
},
{
"key": "ENABLE_BETA",
"label": "Enable beta features",
"inputType": "boolean",
"default": false,
"category": "user_optional"
}
]
}Each entry:
key— UPPER_SNAKE name of the env var. Whatever the tenant types becomesprocess.env.GREETING(oros.environ["GREETING"]).label— the human-readable field label in the wizard.description— optional helper text shown below the label.inputType— one oftext,password,select,number,boolean,textarea(canonical enum:web/src/lib/wizard-input.ts). Drives the rendered input and basic validation. There is noemailorurltype — usetextwith avalidations.patternif you need format checking.default— pre-filled value. The tenant can still change it.category—user_required(the wizard blocks on empty) oruser_optional(can be left blank).options— forinputType: select, the list of{ label, value }choices.validations— optional{ minLength, maxLength, pattern, min, max }constraints.wizardStep— optional group name used bywizardLayout(see below).visibility—visible(default) orsecret(masks the value in the dashboard after submit).
How it shows up at runtime
Wizard inputs become plain env vars. There's no SDK call to fetch them, no special manifest reference:
import os
GREETING = os.environ.get("GREETING", "Hello")
ADMIN_EMAIL = os.environ["ADMIN_EMAIL"]
ENABLE_BETA = os.environ.get("ENABLE_BETA") == "true"const greeting = process.env.GREETING ?? "Hello";
const adminEmail = process.env.ADMIN_EMAIL!;
const enableBeta = process.env.ENABLE_BETA === "true";Boolean inputs are persisted as the string "true" / "false". Compare against the string in your code.
When to use a wizard input vs. an integration
| Use a wizard input for | Use an integration for |
|---|---|
| A value the tenant types once (greeting, name, admin email) | An API key for a third-party provider |
| A feature flag the tenant chooses | A token tied to an OAuth connection |
| A static configuration string | Anything that rotates or refreshes |
If the value comes from another platform — OpenAI, Telegram, Composio, Notion — it's almost certainly an integration, not a wizard input. Wizard inputs are for things the tenant invents; integrations are for things the tenant connects.
Wizard layout
By default, every userInputs[] entry shows up on a single page in the wizard. For longer setup flows you can group inputs into sub-steps with wizardLayout. The real shape is:
{
"wizardLayout": {
"version": 1,
"subSteps": [
{
"id": "connect",
"title": "Connect integrations",
"sections": [
{ "type": "integration", "providers": ["openrouter", "telegram"] }
]
},
{
"id": "configure",
"title": "Basic settings",
"sections": [
{ "type": "wizard_inputs", "groups": ["basic"] }
]
}
]
}
}Section types are deployment_name, workspace, admin_credentials, wizard_inputs (with groups: string[]), integration (with providers: string[]), tool_extras. ["*"] inside groups or providers sweeps up everything not placed in earlier sub-steps. Canonical schema: web/src/lib/wizard-layout.ts. Full reference in Deploy & publish → Wizard layout.
What's NOT a wizard input
Two things tenants type that don't go in userInputs[]:
- The deployment slug. The wizard collects it in a
deployment_namesection before any inputs render. You can't override or rename it from the manifest. - Admin credentials. If your tool needs an initial admin password, declare
adminBootstrapin your template manifest, notuserInputs. The platform handles password generation and seeding.
Tenant-changeable after deploy
Tenants can edit wizard input values from the deployment's settings page after deploy. The platform updates the env var and restarts your container — your code sees the new value on its next read. There's nothing extra to declare; this behavior applies to every userInputs[] entry.