kelviq.config.ts is a plain TypeScript file that declares your pricing catalog. You author it by hand (starting from kelviq init) or generate it from a live environment with kelviq pull — the two are interchangeable starting points.
The format follows one design idea: the file describes the desired state of your catalog, and nothing else. Anything that’s an artifact of a particular deployment — UUIDs, draft/published status, server-generated timestamps — stays out, which is what makes the same file portable across sandbox and production and meaningful to diff over time. It’s TypeScript rather than YAML or JSON for one reason: types. Your editor autocompletes every field, invalid enums fail to compile, and a config that type-checks is a config the CLI can read.
A config file imports three builders from @kelviq/cli/config and exports the results:
product() / feature() / plan() is collected, regardless of its export name. Other exports (helper constants, functions) are ignored, so you can organize the file however you like — split values, comment freely, generate plans in a loop. Duplicate identifiers within a kind are a hard error.
The identity rule
Every cross-reference in the format —plan.product, entitlements[].feature — is an identifier slug, never a UUID. Identifiers are lowercase URL-safe slugs matching ^[a-z0-9][a-z0-9-_]*$ (for example pro, api-calls, seats_v2).
The Kelviq API sometimes wants UUIDs internally; the CLI resolves identifiers to UUIDs at sync time so your config file stays portable — the same file works against sandbox and production, where the same catalog has different UUIDs.
The plan lifecycle (draft → published) is also deliberately absent: the config describes desired state, not deployment state. Lifecycle is handled at sync time.
product()
feature()
plan()
Entitlements
Each entry grants a feature to the plan:
Entitlements accept additional keys beyond these — the shape varies by feature type, and unrecognized keys are passed through to the API rather than rejected.
Some enum values (for example
EVERY_28_DAYS) are only available when enabled for your account. The CLI’s enum fields autocomplete the standard values but accept any string: values it doesn’t recognize are passed through verbatim, and the API validates them when you push. This means new server-side values work without a CLI update.Prices
The price envelope is validated; the charge structures inside it are deliberately loose and passed through to the API as-is:A pulled config reproduces your remote prices in full, including nested charge structures. You rarely need to author
chargeCatalogPrice by hand — pull an existing plan and adapt it. If you do author prices by hand, set freeTrial, trialPeriod, enabled, and taxBehavior explicitly — omitting them makes kelviq push see a difference against the server’s stored defaults on every run (see the push reference).Schemas as values
Everything the builders validate against is exported from@kelviq/cli/config if you want to reuse it — productDefSchema, featureDefSchema, planDefSchema, entitlementDefSchema, priceDefSchema, kelviqConfigSchema, the enums (taxCodeSchema, featureTypeSchema, priceTypeSchema, resetSchema, taxBehaviorSchema, identifierSchema), and their TypeScript types (ProductDef, FeatureDef, PlanDef, and so on). They’re standard Zod schemas.