Skip to main content
A Voyant deployment is described by one manifest: voyant.config.ts. It is the single declaration of what the deployment is, and the framework reads it to assemble the app at build time. This page explains what the manifest declares, how the framework turns it into a running app, and the CLI commands that inspect and gate it. Configuration is the seam that lets a deployment shrink to its identity (its config, the providers it chose, and the small amount it added) instead of being a fork full of hand-maintained glue.

What the manifest declares

voyant.config.ts is config-shaped. A deployment’s manifest declares, at a high level:
KeyWhat it declares
deploymentDeployment-level identity and settings.
projectConfigBranding and locale for the app.
modulesThe modules this deployment mounts. This is the spine the framework composes from.
additionalSchemasExtra schema closures that are migrated but not mounted as runtime modules (optional verticals like charters or cruises that stay on their own cadence).
pluginsDistribution bundles the deployment installs.
featureFlagsDeployment feature toggles.
adminAdmin metadata the dashboard derives nav, routes, and destinations from.
Two things deliberately stay out of the manifest. Provider choices (payments, storage, connectors) are injected by the deployment as runtime wiring, not declared as config, so the framework never bakes in a vendor. And deployment-local custom code lives in discovery seams (src/modules, src/extensions, src/links, src/admin), discovered at build time rather than enumerated in the manifest.
// voyant.config.ts (illustrative shape)
import { defineConfig } from "@voyant-travel/framework"

export default defineConfig({
  projectConfig: { name: "Acme Travel", locale: "en" },
  modules: ["catalog", "bookings", "finance", "legal", "notifications"],
  additionalSchemas: ["cruises"], // migrated, not mounted
  plugins: ["@voyant-travel/plugin-netopia"],
  featureFlags: { storefrontSelfService: true },
  admin: { /* nav and metadata */ },
})

How the framework reads the manifest

The manifest is not decoration. The framework reads it to assemble the app, and it does so at build time because Cloudflare Workers compose statically (no runtime require of arbitrary modules). Three things are derived from it.
1

Schemas

The manifest’s modules, extensions, and additionalSchemas (package closures) plus schemas (starter-local files) resolve into a committed drizzle.schemas.generated.ts, which drizzle.config.ts imports. The schema set is derived, never hand-listed. To change it, edit the manifest and regenerate, do not edit the generated file.
2

Routes

The modules in config.modules mount their route surfaces into the API. Admin routes are generated from the manifest into admin.routes.generated.tsx, so adding a module to the manifest contributes its routes without hand-wiring.
3

Admin chrome

Admin destinations and extension factories are generated from the manifest (admin.destinations.generated.ts, admin.extensions.generated.ts), and module-shipped admin metadata feeds the dashboard nav. Adding a module to config.modules auto-contributes its admin presence.
The direction this enables is a thin deployment: the framework reads config to assemble the standard module set, derives the admin chrome from module metadata, mounts the generated routes, and wires the deployment’s injected providers and extensions. The deployment file collapses toward config plus providers plus its own extensions, with no glue that can silently drift on upgrade.
// The intended thin-deployment shape: config + injected providers + the 20%.
export default createOperatorApp({
  config: voyantConfig,
  providers: { card: netopiaCardStart, storage: r2(env), flights: demoConnector(env) },
  extensions: [],
})
The standard deployment then upgrades by bumping the framework version and running migrations, with no code merge, because config and provider wiring are stable contracts and framework changes arrive as package updates. Custom deployments do the same and reconcile only their own src/ extensions.

Inspecting the manifest

The CLI reads the nearest voyant.config.* and can show it, validate it, or tell you which file resolved.
voyant config show       # print the resolved manifest
voyant config validate   # validate the manifest
voyant config path       # show which voyant.config.* file resolved
Reach for voyant config path first when a deployment is behaving as if it read a different manifest than you expect (a common surprise in a workspace with multiple config files). voyant config validate checks the manifest is well-formed before you rely on anything derived from it. To inspect what the manifest derives on the database side, use the schema and admin commands:
voyant db schemas          # print the manifest-derived schema list
voyant db schemas --emit    # emit the generated schema manifest
voyant admin generate --check        # verify admin extensions are in sync
voyant admin generate --routes --check
voyant admin generate --destinations --check
The --check forms verify the generated artifacts are in sync with the manifest without rewriting them, which is what you want in CI.

The doctor preflight gates

voyant doctor is the single preflight that makes a deployment safe to run and to upgrade. It runs a set of gates and exits non-zero on any failure.
voyant doctor            # run all preflight gates
voyant doctor --strict   # treat warnings as failures
It closes the cheapest, highest-value risks:
  • Env, bindings, and secrets. It validates required env at startup instead of letting a missing value fail at first use as a runtime 500, and it checks env.d.ts against wrangler.jsonc (including catching placeholder values like unreplaced KV ids).
  • Composition drift. It asserts that config.modules, the mounted registry, the derived nav, icons, and destinations, and the generated routes are all in sync, and that every installed module’s migrations are applied. A module present in config.modules but missing an icon, label, destination, or migration fails the gate. This is what keeps an upstream domain from silently vanishing from the nav on upgrade.
Under the hood it composes the narrower doctors, so you can also run them individually:
voyant db doctor --fail-on-drift   # manifest resolvability, schema parity, prefixes, link snapshot
voyant admin doctor                # parity between manifest, admin extensions, and routes/destinations
Make voyant doctor part of CI and run it locally before pushing. It is cheap, independent of the heavier framework machinery, and it is the mechanism that turns “bump versions and migrate” into a safe upgrade rather than a leap of faith.

Admin generation from the manifest

The dashboard is assembled from the manifest, not hand-registered. voyant admin generate emits the generated admin artifacts so adding a module to config.modules contributes its admin presence automatically.
voyant admin generate                 # emit admin.extensions.generated.ts
voyant admin generate --routes        # emit the assembled admin route module
voyant admin generate --destinations  # emit the destination resolver map
Custom admin surfaces a deployment adds (a page, a widget injected into a named slot, or a nav entry) are discovered from src/admin/<name>/ rather than declared in the manifest, the same drop-a-folder build-time discovery used for custom modules and extensions. So the manifest stays the source of truth for the standard admin, while the deployment’s own admin additions live in code it owns and that survives voyant upgrade.

Next steps

Modules

What the manifest’s modules actually compose.

Data models

How the manifest derives the schema set and migrations.

Architecture

Build-time composition and the deployment boundary.

Command reference

Every voyant config, voyant admin, and voyant doctor command.