Create Warlock
create-warlock is the official project scaffolder. One command and a short setup flow later you have a complete, configured Warlock project that boots: choose API-only or full-stack web, then a database unless a database flag already supplied it. Prefer the old style? --interactive restores the full long-form wizard.
Migrating an existing app? Start with the v5 migration guide.
npm create warlock@latest my-appcd my-appWith a terminal, that command asks for the project name (if you did not pass one), API-only or full-stack web, and a database unless --db or --no-db already supplied it. The database selector offers MongoDB and PostgreSQL, shows MySQL as disabled, and includes None for a database-free app. Everything else — package manager, optional features, git, JWT secrets — takes a sensible default, printed as a summary after scaffolding, with the flag to change each one. See Headless / non-interactive mode below for scripting it fully, or --interactive for the question-by-question flow.
What lands is a ready-to-run app: the src/app (one folder per feature) + src/config (one file per subsystem) layout, a warlock.config.ts, decorators already enabled in tsconfig.json, and a dev server you start with npm run dev.
Headless / non-interactive mode
Section titled “Headless / non-interactive mode”create-warlock is fully headless: every prompt has a flag that answers it, --yes takes the default for anything you leave unset, and it works with no terminal at all — CI, a script, an agent. There are three modes:
- Default (a TTY, no
--yes, no--interactive) — asks the project name (if not already given), API-only or full-stack web (--stack), then a database unless--dbor--no-dbalready supplied it. The stack is the only choice that changes the generated app’s shape. --yes(or no TTY at all) — fully headless. Every answer comes from a flag or its default; nothing is ever prompted, and stdin is never read.--interactive(alias--customize) — the full long-form wizard, one prompt at a time: project name, package manager, database driver, features, AI providers, git, JWT.
# Scripting it in CI, or driving it from an agent: one command, zero promptsnpm create warlock@latest my-app -- --yes \ --db=postgres --pm=pnpm --stack=web \ --features=test,redis --ai=ai-openai \ --git --jwt --agents=claude,cursor
# The one structural fork, still headlessnpm create warlock@latest my-web-app -- --yes --stack=web
# The full long-form wizard, one prompt at a timenpm create warlock@latest -- --interactiveThe first positional argument (or --name) is the project name. Value flags accept either --db=postgres or --db postgres. Unknown --db, --features, --ai, --pm, --agents, or --lint values fail fast — before anything is written to disk — so a typo (or a hostile value) never leaves you with a half-scaffolded project.
| Flag | Takes value | Default | Purpose |
|---|---|---|---|
<positional> / --name | yes | — (required) | Project name + target directory |
--stack | yes | api | api or web — the structural choice; web seeds web, shadcn, and design-system by default |
--db / --no-db | yes / no | mongodb | Database driver — mongodb, postgres, or none (--no-db is shorthand for --db=none) |
--pm | yes | inferred (npm_config_user_agent or the system) | Package manager — must be npm / yarn / pnpm / bun; any other value is rejected before scaffolding starts |
--features | yes (CSV) | none, or ["web", "shadcn", "design-system"] when --stack=web and --features is not given | Comma-separated optional feature keys |
--lint | yes | oxc | oxc (Oxlint/Oxfmt); removed or invalid values fail before writes |
--ai | yes (CSV) | none | Comma-separated AI provider keys (auto-pulls @warlock.js/ai) |
--agents | yes (CSV) | claude | Comma-separated agent-kit targets to derive docs/skills for — validated against the published target list, cached after the first lookup |
--git / --no-git | no | off | Force-enable or force-disable git initialization |
--jwt / --no-jwt | no | off | Force-enable or force-disable JWT secret generation |
-y, --yes | no | off | Skip every prompt and accept defaults for anything unset |
--interactive, --customize | no | off | Restore the full long-form interactive wizard |
-h, --help | no | — | Print usage (every flag + its default) and exit |
-v, --version | no | — | Print the installed create-warlock version and exit |
-h/--help and -v/--version short-circuit before anything else runs — no prompt, filesystem write, or network call, and --help wins even over a positional project name.
It fails loudly without a TTY
Section titled “It fails loudly without a TTY”Without a terminal, --yes is not even required — when stdin isn’t a TTY (CI, a script, a container, an agent harness), the scaffolder never tries to prompt, regardless of which mode was requested. If the flags already answer everything it needs, it just scaffolds. Prompting is the fallback, not the precondition.
The one thing that can’t fall back to a default is the project name. With no TTY and no name given (neither positional nor --name), the run fails immediately and names exactly what to pass instead of hanging on a prompt nobody can answer:
A project name is required and no terminal is available to ask for one. Pass it asthe first argument or --name=<name>. Non-interactive flags: --yes, --pm=<npm|yarn|pnpm>,--db=<driver>|--no-db, --features=<list>, --ai=<list>, --lint=<oxc>, --git|--no-git, --jwt|--no-jwt.AI choice in Customize
Section titled “AI choice in Customize”AI remains optional in the long-form wizard. Press Enter with no AI selections
to skip it. The AI multiselect also places None last; choosing it alone has
the same result. None cannot be combined with an AI provider or capability
package, so the wizard shows a warning and returns to the AI selector with
the non-None selections retained for correction. Headless --ai flags are
unchanged.
Exit codes
Section titled “Exit codes”| Exit code | When |
|---|---|
0 | Success — including --help, --version, and a user cancelling an interactive prompt (Ctrl+C / Esc). |
1 | A validation failure (unknown --pm/--db/--features/--ai/--agents/--lint value, missing project name with no TTY to ask), the target directory already exists, or the scaffold itself failed partway through (dependency install, git init, feature addition, or cache warm-up). |
A scaffold failure surfaces the error and halts — there is no automatic rollback of partially-written files.
Framework conventions
Section titled “Framework conventions”The toolchain leaves the framework’s own rules to
warlock check, which runs the
same with or without Oxlint. It reports
request.all() reaching a query or service, mutating routes with no auth
middleware and no public: true, redirects to request-supplied targets without
safeRedirect(), raw emails and tokens in logs, unbounded regexes on request
input, await log.* and .js import extensions, each with a WL code and its
fix. The scaffold itself passes every check: its sign-in and password-recovery
routes say public: true and are throttled, and its user list validates its
query. The generated AGENTS.md tells coding agents to run npx warlock check
after every change.
Oxlint and Oxfmt
Section titled “Oxlint and Oxfmt”Oxlint 1.88.0 and Oxfmt 0.73.0 are the only supported tools. New apps
pin TypeScript 7.0.2 and include .oxlintrc.json, .oxfmtrc.json,
.oxfmtignore, and VS Code settings for oxc.oxc-vscode.
--lint=oxc is optional; --lint=eslint fails with the 5.34.0 removal message.
The wizard has no toolchain choice.
npm create warlock@latest my-app -- --yescd my-appnpm run lint:checknpm run format:checkOxlint enables "categories": { "correctness": "error" }, unused-variable/import
checks, explicit class accessibility and type-import style. Oxfmt sorts imports
with "sortImports": { "newlinesBetween": false, "internalPattern": ["app/", "web/", "@shared/"] }.
It keeps built-ins, packages, internal aliases and relative imports in one block.
Formatting retains unused imports; lint reports them. Local package scripts use
oxlint, oxfmt and tsc directly. The non-mutating scripts are lint:check
and format:check. Background checks run TypeScript and configured Oxlint.
Scaffolding without a database
Section titled “Scaffolding without a database”Not every app needs a database. Pick None in the wizard’s database step — or pass --db=none (--no-db is a shorthand) — and the scaffold leaves the database out entirely: no driver is selected, no driver package is installed, and the template’s src/config/database.ts is removed. Warlock’s database connector is gated on that config file, so the app boots cleanly with no database wired.
# A database-free API in one commandnpm create warlock@latest my-api -- --yes --no-db --features=testpnpm versions
Section titled “pnpm versions”With --pm=pnpm, create-warlock reads pnpm --version and writes the pnpm settings that version understands, so esbuild’s install script (which warlock dev and warlock build need) is allowed and installs succeed:
| pnpm | What the app gets |
|---|---|
| 10.26+, 11, 12 | pnpm-workspace.yaml with allowBuilds (and minimumReleaseAgeExclude for @warlock.js/*) |
| 10.5 – 10.25 | the same file, plus an onlyBuiltDependencies list, which these versions read instead |
| 7 – 9, 10.0–10.4 | no pnpm-workspace.yaml (these versions reject one without packages:); pnpm.onlyBuiltDependencies in package.json |
Inside an existing pnpm workspace the app gets neither: the parent workspace’s settings apply. If you upgrade pnpm later, move the approvals to the row for the new version.
Where to go next
Section titled “Where to go next”- Full walkthrough → Core · Installation — every question, the scaffolded file tree, and booting the dev server, step by step.
- Your first endpoint → Core · Getting Started — from a fresh project to a working route.
- Scaffold as you build → Core · Generators —
warlock generate.module,generate.controller, and the rest of the family.
TypeScript 7 in 5.32
Section titled “TypeScript 7 in 5.32”Default Oxlint/Oxfmt projects pin typescript to 7.0.2; tsc is the native Go compiler.
The template uses module: "ESNext", moduleResolution: "bundler", explicit
Node types, and paths relative to its tsconfig, without baseUrl.
Existing apps must remove baseUrl, rebase alias targets, and replace
moduleResolution: "node" / "node10" before typechecking.
Migrate existing apps
Section titled “Migrate existing apps”ESLint and Prettier support was removed in 5.34.0. Oxlint/Oxfmt is the only
supported toolchain. --lint eslint and healthCheckers.eslint are removed.
Install the new core release and ts-morph, then migrate an existing app:
npm install --save-dev ts-morphnpx warlock upgrade --from 5.33 --to 5.34.0 --dry-runnpx warlock upgrade --from 5.33 --to 5.34.0npm installnpm run formatnpm run lint:checknpx warlock checkThe oxc-toolchain-removal step reapplies the preserved 5.33 oxc-toolchain
migration when legacy configs, scripts or dependencies remain. Apps upgrading
from 5.32 can still use npx warlock upgrade --from 5.32 --to 5.33.0 first.
It replaces standard scripts/dependencies, pins TypeScript 7.0.2, updates generated
editor settings, removes healthCheckers.eslint and selects Oxlint. A generated
.prettierignore becomes .oxfmtignore; Oxfmt scripts read it explicitly.
Literal custom rules are translated without executing the config. Per-folder
no-restricted-imports and no-restricted-exports become Oxlint overrides with
file/exclusion patterns. Supported @typescript-eslint/* rules become native
typescript/* rules (or native base rules). Options are checked against the
pinned Oxlint 1.88.0 schema. Type-aware equivalents enable options.typeAware
and add oxlint-tsgolint@7.0.2004 to the app’s dev dependencies.
unused-imports/no-unused-imports uses no-unused-vars with safe import fixes;
Oxlint shares the severity for imports and variables, so differing active
severities are reported for review.
Only unsupported rules/options, computed or imported settings, selectors such
as no-restricted-syntax, and conflicting existing Oxc rules need manual review.
Each finding names its source file:line, rule and affected file patterns.
Fully ported lint configs are removed; configs with findings remain for reference
until those findings are addressed. Existing Oxc rules are kept on conflict.
--dry-run --json reports the same plan without writes. Customized formatting,
editor settings and nonstandard scripts can also require review.
Upgrading: new and migrated template configs enable
"categories": { "correctness": "error" }. Address the additional diagnostics;
existing Oxc configs retain their categories. A real lockfile beside a workspace
marker determines the installer: package-lock.json plus pnpm-workspace.yaml
without pnpm-lock.yaml now produces npm install.
Project skills and starter contracts
Section titled “Project skills and starter contracts”Generated apps include nine project skills for API design, TypeScript standards, persistence, Git workflow, module boundaries, resilience, security, testing, and web standards. Each discovery description stays within 450 characters and names a sibling skill for adjacent work. References use skill names and package topic names so agent-kit can resolve the installed project and package skill names.
New domain fields and resource output use camelCase; driver-managed timestamp storage can retain snake_case. The sample User and Post models keep Cascade’s numeric primary keys internally and generate a separate publicId UUID server-side. Their resources expose that value as id; post update routes and user ID filters resolve it through publicId. Cascade’s uuid getter only stringifies a primary key. For legacy stored columns, a resource schema entry such as totalCents: ["total_cents", "int"] emits camelCase without renaming the database column.
Both model schemas declare publicId with v.string().uuid().default(() => randomUUID()),
using randomUUID from node:crypto. The callback generates a fresh UUID for each
record; the migrations enforce uniqueness with string(36).unique().
The HTTP config combines errorMapper for opaque unexpected thrown errors with errorResponse for every JSON error response. The public contract is { error: { code, message }, details?, requestId }, including framework-generated 404, 422, 429, and 500 responses. Validation issues appear under details; 5xx messages stay opaque and diagnostic payloads/stacks are omitted. Routed errors carry Warlock’s real request ID; early Fastify rejections use its request ID, echoed in the same correlation header. HTML error pages and streams keep their own contracts. See HTTP response formatting.
The contact, locale, user create/list and post create/update examples declare responseSchema for generated client types and OpenAPI; the API design skill requires this declaration for new controllers. Side-effecting API contracts require Idempotency-Key; authenticate before idempotency middleware, enforce the header in app middleware, and use shared storage for retries across processes. The contact demo remains a side-effect-free validation example. Its locale interaction, field wiring, and submit state live in feature hooks, with one component per file and shared props types.
Oxlint enables typescript/no-explicit-any, explicit member accessibility, curly, and no-console. The hybrid interface-for-contracts/type-for-data convention stays a review rule because consistent-type-definitions only supports one global preference. App sub-folder barrels remain permitted; web imports target individual component and hook files to keep client bundles lean and server-only code out of barrels. Run npm run lint:check and npm run format:check in the generated app.
These model/resource conventions apply to newly generated apps. Existing apps adopting public UUIDs must add and backfill a unique column before changing their resource output or route lookups; updating a skill alone does not migrate stored data.
Client asset limits
Section titled “Client asset limits”The generated warlock.config.ts configures build.budgets.page with gzip
limits of 150kb JavaScript and 50kb CSS in mode: "error". API-only
builds have no client pages to check. Adjust the limits or choose "warn"
as needed; see client performance budgets.
Starter browser checks
Section titled “Starter browser checks”Full-stack web projects include e2e/home.e2e.ts. Run npx warlock e2e with
Chrome or Edge installed. The spec builds and starts an owned app copy, visits
/ at 1280×800 and 390×844, then calls page.assertAccessible(),
page.assertViewport() and page.screenshot("home"). API-only scaffolds omit
this web spec. The fixture excludes dotenv files and production data directories;
provide disposable service configuration explicitly if you add database-backed
features. A database-free scaffold needs no external database.
Test projects
Section titled “Test projects”npm create warlock@latest my-project -- --features=test uses warlock add test
to generate separate Vitest projects. unit discovers src/**/*.spec.ts outside
tests/ with no framework or HTTP setup. integration discovers *.test.ts and
specs under tests/ with the framework setup and a worker cleanup runner.
The template pins Vitest 5.0.3 for that runner API. Existing configs are preserved.
npx vitest run --project unitnpx warlock test --changed --since "$LAST_RELEASE_REF"npx vitest run --project integration# Full suite nightly:npx vitest runThe default runtime lifetime is per file. For reuse, profiling and the
quota-aware recommendedTestWorkers() helper, see the
testing guide.
The generated testing-strategy project skill covers the same trade-offs.
App-owned form controls
Section titled “App-owned form controls”The full-stack preset (--stack=web) now seeds web, shadcn, and
design-system. Selecting shadcn in Customize or passing
--features=tailwind,shadcn also adds the design system. An explicit
--features replaces the stack’s default selection.
The generated app receives fourteen accessible Mongez controls under
src/web/design-system/components, with separate hooks and props types. Each
component is a default export imported through the web alias, for example
import EmailInput from "web/design-system/components/email-input";. Forms use
Form directly from @mongez/react-form. shadcn primitives and exact runtime
dependency versions are copied into the app; existing source is preserved.
See Shared form controls
for the component list, props, value shapes, keyboard and date semantics.
Existing Tailwind apps can use npx warlock add design-system.
Starter controllers declare responseSchema for their success and explicit error statuses. Keep those contracts aligned when changing controller responses, including the token payloads in authentication controllers.
The interactive feature picker uses compact labels and drops hints when space is limited, including the existing-Tailwind design controls option on a 40-column terminal.
When submitting a generated form asynchronously, return the save promise from onSubmit so Mongez clears its submitting state on completion and allows another submission. A synchronous handler must manage that state explicitly.