{ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "https://manifest.lang/spec/config/manifest.config.schema.json", "title": "Manifest Build Configuration", "description": "Schema for manifest.config.yaml / manifest.config.yml / .manifestrc.yaml. The runtime-level TypeScript config (manifest.config.ts) is validated structurally by the loader, not by this schema — it contains JavaScript functions and class references that cannot be expressed in JSON.", "type": "object", "additionalProperties": false, "properties": { "$schema": { "type": "string", "description": "Optional pointer to this schema for editor IntelliSense." }, "src": { "type": "string", "description": "Glob pattern for source .manifest files.", "default": "**/*.manifest" }, "output": { "type": "string", "description": "Directory where compiled IR JSON files are written.", "default": "ir/" }, "prismaSchema": { "type": "string", "description": "Optional path to a Prisma schema file used for property alignment checks. When omitted, Manifest searches prisma/schema.prisma, schema.prisma, and db/schema.prisma." }, "projections": { "type": "object", "description": "Per-projection configuration. Each key matches a registered projection name (e.g. 'nextjs', 'routes').", "additionalProperties": false, "properties": { "nextjs": { "$ref": "#/definitions/NextJsProjectionConfig" }, "routes": { "$ref": "#/definitions/RoutesProjectionConfig" }, "prisma": { "$ref": "#/definitions/PrismaProjectionConfig" }, "prisma-store": { "$ref": "#/definitions/PrismaStoreProjectionConfig" } } }, "env": { "$ref": "#/definitions/EnvironmentMapping", "description": "Environment variable mapping for stores, auth, adapters, and custom settings." }, "hooks": { "$ref": "#/definitions/HooksConfig", "description": "Git pre-commit hook settings consumed by `manifest install-hooks`." }, "plugins": { "type": "array", "description": "Third-party plugin declarations loaded by the CLI. Inspected via `manifest plugins`.", "items": { "$ref": "#/definitions/PluginDeclaration" } }, "naming": { "$ref": "#/definitions/NamingConvention", "description": "Global identifier-casing convention inherited by projections that map IR names to physical database names (currently Prisma). A per-projection `projections..options.naming` overrides it." } }, "definitions": { "HooksConfig": { "type": "object", "additionalProperties": false, "description": "Settings for `manifest install-hooks` (git pre-commit integration).", "properties": { "skipInCi": { "type": "boolean", "description": "Skip running the generated hook in CI environments.", "default": true }, "provider": { "type": "string", "enum": ["husky", "simple-git-hooks"], "description": "Git hook manager the pre-commit hook is installed into.", "default": "husky" }, "runFmt": { "type": "boolean", "description": "Run `manifest fmt` from the generated pre-commit hook.", "default": true }, "runValidate": { "type": "boolean", "description": "Run `manifest validate` from the generated pre-commit hook.", "default": true } } }, "PluginDeclaration": { "type": "object", "additionalProperties": false, "required": ["module"], "description": "Declares a Manifest plugin for the CLI to load.", "properties": { "module": { "type": "string", "description": "npm package name or relative file path to the plugin module." }, "options": { "type": "object", "description": "Plugin-specific options passed to the plugin at load time." }, "enabled": { "type": "boolean", "description": "Whether the plugin is active.", "default": true } } }, "EnvVarDefinition": { "type": "object", "description": "Definition of a single environment variable referenced by the application.", "additionalProperties": false, "required": ["name"], "properties": { "name": { "type": "string", "description": "Environment variable name (e.g. DATABASE_URL)." }, "description": { "type": "string", "description": "Human-readable description of the variable's purpose." }, "required": { "type": "boolean", "description": "Whether the variable must be set at startup. Defaults to true.", "default": true }, "default": { "type": "string", "description": "Default value if the variable is not set." }, "example": { "type": "string", "description": "Example value for .env.example generation." } } }, "EnvironmentMapping": { "type": "object", "description": "Grouped environment variable definitions for preflight validation.", "additionalProperties": false, "properties": { "stores": { "type": "object", "additionalProperties": { "$ref": "#/definitions/EnvVarDefinition" } }, "auth": { "type": "object", "additionalProperties": { "$ref": "#/definitions/EnvVarDefinition" } }, "adapters": { "type": "object", "additionalProperties": { "$ref": "#/definitions/EnvVarDefinition" } }, "custom": { "type": "object", "additionalProperties": { "$ref": "#/definitions/EnvVarDefinition" } } } }, "NextJsProjectionConfig": { "type": "object", "description": "Configuration for the Next.js App Router projection.", "additionalProperties": false, "properties": { "output": { "type": "string", "description": "Directory where generated TypeScript files are written.", "default": "generated/" }, "options": { "$ref": "#/definitions/NextJsProjectionOptions" } } }, "NextJsProjectionOptions": { "type": "object", "description": "Surface-specific options for the Next.js projection. Every key here has a default exported from src/manifest/projections/nextjs/defaults.ts.", "additionalProperties": false, "properties": { "authProvider": { "type": "string", "enum": ["clerk", "nextauth", "custom", "none"], "description": "Auth provider that wraps generated route handlers.", "default": "none" }, "authImportPath": { "type": "string", "description": "Module path for the auth helpers (auth(), getServerSession, etc.).", "default": "@/lib/auth" }, "databaseImportPath": { "type": "string", "description": "Module path for the database client used by direct-read routes.", "default": "@/lib/database" }, "responseImportPath": { "type": "string", "description": "Module path for manifestErrorResponse / manifestSuccessResponse / normalizeCommandResult helpers.", "default": "@/lib/manifest-response" }, "runtimeImportPath": { "type": "string", "description": "Module path for createManifestRuntime. Only used when dispatcher.executionMode is 'inline'.", "default": "@/lib/manifest-runtime" }, "includeTenantFilter": { "type": "boolean", "description": "When true, generated read routes filter by tenantIdProperty and POST handlers resolve a tenantId before executing.", "default": false }, "includeSoftDeleteFilter": { "type": "boolean", "description": "When true, generated read routes filter by `${deletedAtProperty}: null`.", "default": false }, "tenantIdProperty": { "type": "string", "description": "Name of the tenant scope property used in WHERE clauses and tenant context.", "default": "tenantId" }, "deletedAtProperty": { "type": "string", "description": "Name of the soft-delete timestamp property.", "default": "deletedAt" }, "appDir": { "type": "string", "description": "App Router base directory. Generated route pathHints are relative to this.", "default": "app/api" }, "strictMode": { "type": "boolean", "description": "Whether generated TypeScript should be strict-mode friendly.", "default": true }, "includeComments": { "type": "boolean", "description": "Whether to emit explanatory comments above generated handlers.", "default": true }, "indentSize": { "type": "integer", "minimum": 1, "maximum": 8, "description": "Spaces of indentation in generated code.", "default": 2 }, "unauthorizedStatus": { "type": "integer", "minimum": 400, "maximum": 499, "description": "HTTP status returned when the auth helper rejects the request OR when it throws (invalid/expired token). Auth failures MUST NEVER surface as 500.", "default": 401 }, "generatedDir": { "type": "string", "description": "Base directory for generated non-route artifacts (types, client, hooks, shared-runtime). Individual paths can be overridden via the 'paths' object.", "default": "src" }, "paths": { "type": "object", "additionalProperties": false, "description": "Fine-grained overrides for individual artifact pathHints. When set, takes precedence over generatedDir.", "properties": { "typesFile": { "type": "string", "description": "pathHint for ts.types artifact. Default: '${generatedDir}/types/manifest-generated.ts'" }, "clientFile": { "type": "string", "description": "pathHint for ts.client artifact. Default: '${generatedDir}/lib/manifest-client.ts'" }, "hooksDir": { "type": "string", "description": "Base directory for subscription hooks. Default: '${generatedDir}/hooks'" }, "sharedRuntimeFile": { "type": "string", "description": "pathHint for nextjs.sharedRuntime artifact. Default: '${generatedDir}/lib/manifest-shared-runtime.ts'" } } }, "tenantProvider": { "type": "object", "additionalProperties": false, "description": "Override the default userTenantMapping.findUnique pattern with a project-supplied tenant lookup helper.", "properties": { "importPath": { "type": "string", "description": "Module path for the lookup helper." }, "functionName": { "type": "string", "description": "Named export to call (e.g. getTenantIdForOrg)." }, "lookupKey": { "type": "string", "enum": ["orgId", "userId"], "description": "Which auth-context field to pass as the lookup key." } }, "required": ["importPath", "functionName", "lookupKey"] }, "dispatcher": { "$ref": "#/definitions/DispatcherOptions" }, "concreteCommandRoutes": { "$ref": "#/definitions/ConcreteCommandRoutesOptions" }, "readRoutes": { "$ref": "#/definitions/ReadRoutesOptions" }, "naming": { "$ref": "#/definitions/NamingConvention", "description": "Naming convention for database accessor names (database. in generated read routes). Applies table-name resolution to the entity name — use when the database client exposes physical table names (Kysely, raw SQL) instead of Prisma model delegates. Response field names stay camelCase (the API contract is independent of physical DB naming). Explicit accessorNames entries override it." }, "accessorNames": { "type": "object", "additionalProperties": { "type": "string" }, "description": "Explicit per-entity database accessor overrides, e.g. { \"OrderLine\": \"order_lines\" } → database.order_lines.findMany(...). Takes precedence over 'naming'." }, "routeSegments": { "type": "object", "additionalProperties": { "type": "string" }, "description": "Explicit per-entity URL path segment overrides for generated routes and client fetch paths, e.g. { \"OrderLine\": \"order-lines\" } → app/api/order-lines/list/route.ts. Default: lowercased entity name." } } }, "DispatcherOptions": { "type": "object", "additionalProperties": false, "description": "Configuration for the canonical write surface at POST /api/manifest/[entity]/commands/[command]. See docs/spec/config/manifest.config.md § Dispatcher.", "properties": { "enabled": { "type": "boolean", "description": "When false, the nextjs.dispatcher surface emits no artifact.", "default": true }, "executionMode": { "type": "string", "enum": ["inline", "externalExecutor"], "description": "Where command execution happens. 'inline' constructs createManifestRuntime per request (back-compat default). 'externalExecutor' imports the configured executor and delegates — the dispatcher does not inline runtime construction.", "default": "inline" }, "executorImportPath": { "type": "string", "description": "Module path for the external executor. Only used in externalExecutor mode.", "default": "@/lib/manifest-executor" }, "executorImportName": { "type": "string", "description": "Named export to call on the external executor module.", "default": "executeManifestCommand" }, "deriveInstanceId": { "type": "boolean", "description": "When true (default), the dispatcher extracts an instanceId from body.instanceId or body.id and forwards it to runCommand / the executor. Non-create commands need this to address the target instance; create commands ignore it harmlessly.", "default": true }, "path": { "type": "string", "description": "Dispatcher route path relative to appDir. Override only if your app uses a non-canonical prefix (e.g. /api/v1/manifest/...).", "default": "/manifest/[entity]/commands/[command]/route.ts" } } }, "ConcreteCommandRoutesOptions": { "type": "object", "additionalProperties": false, "description": "Policy for the deprecated per-command 'concrete' routes (the nextjs.command surface). Opt-in: the canonical dispatcher is the single write surface by default.", "properties": { "enabled": { "type": "boolean", "description": "When false (default), nextjs.command artifacts are suppressed entirely and 'manifest generate --surface all' does not emit them. Opt in only if you still need per-command routes for legacy callers.", "default": false }, "legacyAliasesOnly": { "type": "boolean", "description": "When true (default), emitted concrete routes carry the DEPRECATED ALIAS banner pointing at the dispatcher. Set false only if you intentionally treat per-command routes as a first-class surface.", "default": true } } }, "ReadRoutesOptions": { "type": "object", "additionalProperties": false, "description": "Policy for direct database read routes (GET list, GET detail). Direct reads bypass the runtime engine and assume a Prisma-compatible client at databaseImportPath.", "properties": { "enabled": { "type": "boolean", "description": "When false, nextjs.route and nextjs.detail surfaces emit no artifact (info diagnostic only).", "default": true }, "directDbReads": { "type": "boolean", "description": "When false, read route stubs are emitted without inlining a Prisma call — useful for projects that route reads through a separate query layer.", "default": true } } }, "RoutesProjectionConfig": { "type": "object", "additionalProperties": false, "description": "Configuration for the Canonical Routes projection (routes.manifest, routes.ts).", "properties": { "output": { "type": "string", "description": "Directory where the route manifest and typed path builders are written.", "default": "generated/" }, "options": { "$ref": "#/definitions/RoutesProjectionOptions" } } }, "RoutesProjectionOptions": { "type": "object", "additionalProperties": false, "description": "Options for the Canonical Routes projection. Defaults mirror src/manifest/projections/nextjs/defaults.ts → ROUTES_DEFAULTS.", "properties": { "basePath": { "type": "string", "description": "Base path prefix prepended to every route.", "default": "/api" }, "includeAuth": { "type": "boolean", "description": "Whether route entries carry auth=true expectations.", "default": true }, "includeTenant": { "type": "boolean", "description": "Whether route entries carry tenant=true expectations.", "default": true }, "manualRoutes": { "type": "array", "description": "Hand-declared routes merged into the canonical route surface.", "items": { "$ref": "#/definitions/ManualRouteDeclaration" } } } }, "ManualRouteDeclaration": { "type": "object", "additionalProperties": false, "required": ["id", "path", "method"], "properties": { "id": { "type": "string" }, "path": { "type": "string" }, "method": { "type": "string", "enum": ["GET", "POST", "PUT", "PATCH", "DELETE"] }, "auth": { "type": "boolean", "default": false }, "tenant": { "type": "boolean", "default": false }, "params": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": ["name", "type", "location"], "properties": { "name": { "type": "string" }, "type": { "type": "string" }, "location": { "type": "string", "enum": ["path", "query", "body"] }, "required": { "type": "boolean" } } } } } }, "PrismaProjectionConfig": { "type": "object", "description": "Configuration for the Prisma schema projection.", "additionalProperties": false, "properties": { "output": { "type": "string", "description": "Path hint for the emitted schema.prisma artifact.", "default": "schema.prisma" }, "options": { "$ref": "#/definitions/PrismaProjectionOptions" } } }, "PrismaStoreProjectionConfig": { "type": "object", "description": "Configuration for the Prisma store metadata/registry projection (GenericPrismaStore).", "additionalProperties": false, "properties": { "options": { "$ref": "#/definitions/PrismaStoreProjectionOptions" } } }, "PrismaStoreProjectionOptions": { "type": "object", "description": "Options for prisma-store.metadata and prisma-store.registry surfaces. Mirrors the TypeScript PrismaStoreProjectionOptions = PrismaProjectionOptions & { ...own }. NOTE: draft-07 cannot express closed-set inheritance via allOf — each branch's additionalProperties is blind to the other branch's properties, so a composed schema falsely rejects inherited options (provider, naming, ...). The inherited PrismaProjectionOptions properties are therefore inlined here. Keep in sync with PrismaProjectionOptions.", "additionalProperties": false, "properties": { "provider": { "description": "Optional Prisma datasource provider. When set, the projection emits a datasource block and a prisma.config.ts companion.", "enum": ["postgresql", "mysql", "sqlite", "sqlserver", "mongodb", "cockroachdb"] }, "output": { "type": "string", "description": "Path hint for the emitted schema.prisma artifact. Default: 'schema.prisma'." }, "urlEnvVar": { "type": "string", "description": "Environment variable name for the database URL in the emitted prisma.config.ts companion. Default: 'DATABASE_URL'. Only relevant when provider is set." }, "tableMappings": { "description": "Per-entity table-name override. Emits @@map(\"...\").", "type": "object", "additionalProperties": { "type": "string" } }, "columnMappings": { "description": "Per-entity, per-property column-name override. Emits @map(\"...\").", "type": "object", "additionalProperties": { "type": "object", "additionalProperties": { "type": "string" } } }, "precision": { "description": "Per-entity, per-property decimal precision and scale.", "type": "object", "additionalProperties": { "type": "object", "additionalProperties": { "type": "object", "required": ["precision", "scale"], "additionalProperties": false, "properties": { "precision": { "type": "integer", "minimum": 1 }, "scale": { "type": "integer", "minimum": 0 } } } } }, "indexes": { "description": "Per-entity composite/named index definitions. Each entry is an array of column names or an object with fields and optional name.", "type": "object", "additionalProperties": { "type": "array", "items": { "oneOf": [ { "type": "array", "items": { "type": "string" }, "minItems": 1 }, { "type": "object", "required": ["fields"], "additionalProperties": false, "properties": { "fields": { "type": "array", "items": { "type": "string" }, "minItems": 1 }, "name": { "type": "string" } } } ] } } }, "typeMappings": { "description": "Per-entity, per-property Prisma scalar override. Value is a literal Prisma scalar (e.g. 'Int', 'Decimal').", "type": "object", "additionalProperties": { "type": "object", "additionalProperties": { "type": "string" } } }, "foreignKeys": { "description": "Per-entity, per-relationship FK field name or structured config. Value is a plain string or an object with fields/references/onDelete/onUpdate.", "type": "object", "additionalProperties": { "type": "object", "additionalProperties": { "oneOf": [ { "type": "string" }, { "type": "object", "required": ["fields"], "additionalProperties": false, "properties": { "fields": { "type": "array", "items": { "type": "string" }, "minItems": 1 }, "references": { "type": "array", "items": { "type": "string" }, "minItems": 1 }, "onDelete": { "type": "string" }, "onUpdate": { "type": "string" } } } ] } } }, "dbAttributes": { "description": "Per-entity, per-property native @db.* attribute string (without the @db. prefix). Applied after the type scalar.", "type": "object", "additionalProperties": { "type": "object", "additionalProperties": { "type": "string" } } }, "fieldAttributes": { "description": "Per-entity, per-property array of verbatim Prisma field attributes (e.g. '@unique', '@updatedAt').", "type": "object", "additionalProperties": { "type": "object", "additionalProperties": { "type": "array", "items": { "type": "string" } } } }, "naming": { "$ref": "#/definitions/NamingConvention", "description": "Automatic identifier-casing convention. Opt-in; only ever adds @map/@@map (model/field identifiers stay the IR name). Explicit tableMappings/columnMappings override it." }, "accessorNames": { "description": "Per-entity Prisma client delegate override (e.g. OrderLine -> order_lines).", "type": "object", "additionalProperties": { "type": "string" } }, "metadataOutput": { "type": "string", "description": "Path hint for emitted prisma-model-metadata.generated.ts.", "default": "prisma-model-metadata.generated.ts" }, "registryOutput": { "type": "string", "description": "Path hint for emitted prisma-store-registry.generated.ts.", "default": "prisma-store-registry.generated.ts" }, "storeImportPath": { "type": "string", "description": "Import path for GenericPrismaStore in the registry artifact." }, "metadataImportPath": { "type": "string", "description": "Import path for the metadata module in the registry artifact." }, "softDelete": { "description": "Per-entity status-based soft-delete. Keyed by entity name; `field` is the IR status property name and `deletedValue` the sentinel meaning deleted. Lets an entity that soft-deletes via a status transition (instead of a deletedAt timestamp) use the generic store.", "type": "object", "additionalProperties": { "type": "object", "additionalProperties": false, "required": ["field", "deletedValue"], "properties": { "field": { "type": "string" }, "deletedValue": { "type": "string" } } } } } }, "PrismaProjectionOptions": { "type": "object", "description": "Options for the Prisma schema projection. All keys are optional; omitting a key applies the documented default.", "additionalProperties": false, "properties": { "provider": { "description": "Optional Prisma datasource provider. When set, the projection emits a datasource block and a prisma.config.ts companion.", "enum": ["postgresql", "mysql", "sqlite", "sqlserver", "mongodb", "cockroachdb"] }, "output": { "type": "string", "description": "Path hint for the emitted schema.prisma artifact. Default: 'schema.prisma'." }, "urlEnvVar": { "type": "string", "description": "Environment variable name for the database URL in the emitted prisma.config.ts companion. Default: 'DATABASE_URL'. Only relevant when provider is set." }, "tableMappings": { "description": "Per-entity table-name override. Emits @@map(\"...\").", "type": "object", "additionalProperties": { "type": "string" } }, "columnMappings": { "description": "Per-entity, per-property column-name override. Emits @map(\"...\").", "type": "object", "additionalProperties": { "type": "object", "additionalProperties": { "type": "string" } } }, "precision": { "description": "Per-entity, per-property decimal precision and scale.", "type": "object", "additionalProperties": { "type": "object", "additionalProperties": { "type": "object", "required": ["precision", "scale"], "additionalProperties": false, "properties": { "precision": { "type": "integer", "minimum": 1 }, "scale": { "type": "integer", "minimum": 0 } } } } }, "indexes": { "description": "Per-entity composite/named index definitions. Each entry is an array of column names or an object with fields and optional name.", "type": "object", "additionalProperties": { "type": "array", "items": { "oneOf": [ { "type": "array", "items": { "type": "string" }, "minItems": 1 }, { "type": "object", "required": ["fields"], "additionalProperties": false, "properties": { "fields": { "type": "array", "items": { "type": "string" }, "minItems": 1 }, "name": { "type": "string" } } } ] } } }, "typeMappings": { "description": "Per-entity, per-property Prisma scalar override. Value is a literal Prisma scalar (e.g. 'Int', 'Decimal').", "type": "object", "additionalProperties": { "type": "object", "additionalProperties": { "type": "string" } } }, "foreignKeys": { "description": "Per-entity, per-relationship FK field name or structured config. Value is a plain string or an object with fields/references/onDelete/onUpdate.", "type": "object", "additionalProperties": { "type": "object", "additionalProperties": { "oneOf": [ { "type": "string" }, { "type": "object", "required": ["fields"], "additionalProperties": false, "properties": { "fields": { "type": "array", "items": { "type": "string" }, "minItems": 1 }, "references": { "type": "array", "items": { "type": "string" }, "minItems": 1 }, "onDelete": { "type": "string" }, "onUpdate": { "type": "string" } } } ] } } }, "dbAttributes": { "description": "Per-entity, per-property native @db.* attribute string (without the @db. prefix). Applied after the type scalar.", "type": "object", "additionalProperties": { "type": "object", "additionalProperties": { "type": "string" } } }, "fieldAttributes": { "description": "Per-entity, per-property array of verbatim Prisma field attributes (e.g. '@unique', '@updatedAt').", "type": "object", "additionalProperties": { "type": "object", "additionalProperties": { "type": "array", "items": { "type": "string" } } } }, "naming": { "$ref": "#/definitions/NamingConvention", "description": "Automatic identifier-casing convention. Opt-in; only ever adds @map/@@map (model/field identifiers stay the IR name). Explicit tableMappings/columnMappings override it." } } }, "NamingConvention": { "description": "Identifier-casing convention. String shorthand 'snake_case' expands to { table: 'snake_case', column: 'snake_case', pluralizeTables: true }.", "oneOf": [ { "type": "string", "enum": ["snake_case"] }, { "type": "object", "additionalProperties": false, "properties": { "table": { "description": "Case style for table/model physical names. Default 'preserve'.", "enum": ["snake_case", "camelCase", "PascalCase", "preserve"] }, "column": { "description": "Case style for column/field physical names. Default 'preserve'.", "enum": ["snake_case", "camelCase", "preserve"] }, "pluralizeTables": { "description": "Pluralize resolved table names. Default true.", "type": "boolean" } } } ] } } }