Create
@modularityjs/create scaffolds a new application that consumes the framework. It writes the project files, wires a working boot script for the contracts you pick, runs pnpm install, generates an AGENTS.md, and initialises git — so the next command after scaffolding is pnpm dev.
Use this when you're starting a new app. To add a package inside the framework monorepo itself, see the /new-package skill.
Run
pnpm create @modularityjs my-appThis downloads the scaffolder, runs it interactively, writes the project into ./my-app, installs dependencies, generates AGENTS.md, and initialises a git repository.
Non-interactive (defaults — Fastify + Zod + console logger, no cache / database / session):
pnpm create @modularityjs my-app --yesPresets
A preset pre-fills a coherent set of options; individual flags still win where they overlap.
| Preset | Stack |
|---|---|
api | Fastify + Zod + console + middleware: cors, compression, security-headers + features: health |
htmx | Fastify + Zod + console + middleware: compression, security-headers, formbody + features: health |
worker | No HTTP + Zod + console + features: events, scheduler, health |
pnpm create @modularityjs my-app --preset=api --yes
pnpm create @modularityjs my-app --preset=worker --cache=redis --yesFlags
pnpm create @modularityjs <name> [options]
Positional:
<name> App name (lowercase, digits, hyphens).
Options:
--yes, -y Skip all prompts and use defaults.
--force, -f Overwrite a non-empty target directory.
--no-install Skip `pnpm install` after scaffolding.
--no-git Skip `git init`.
--preset=<value> api | htmx | worker — pre-fills a coherent stack.
--http=<value> fastify | none (default fastify)
--validation=<value> zod | none (default zod)
--logger=<value> console | none (default console)
--cache=<value> memory | redis | none (default none)
--database=<value> typeorm-sqlite | typeorm-postgres | prisma | none (default none)
--session=<value> memory | redis | none (default none)
--auth=<value> jwt | none (default none)
--authz=<value> rbac | policy | none (default none, requires --auth)
--mfa=<list> totp, sms, email, backup-codes, webauthn (requires --auth)
--secrets=<value> memory | vault | aws | gcp | none (default none)
--encryption=<value> aes | none (default none)
--mail=<value> memory | nodemailer | none (default none)
--sms=<value> memory | twilio | none (default none)
--notification=<list> mail, sms (each channel requires the matching contract)
--queue=<value> memory | redis | none (default none)
--webhook=<value> memory | direct | none (default none)
--outbox=<value> memory | typeorm | prisma | none (typeorm/prisma require --database)
--storage=<value> memory | local | s3 | none (default none)
--template=<value> handlebars | ejs | none (default none)
--assets=<value> local | none (default none)
--media=<list> image, video, audio
--ws=<value> fastify | none (requires --http=fastify)
--i18n=<value> json | none (default none)
--feature-flags=<value> static | growthbook | none (default none)
--rate-limit=<value> memory | redis | none (default none)
--middleware=<list> Comma-separated. cors, compression, security-headers, formbody, upload
--features=<list> Comma-separated. cli, events, scheduler, telemetry, health
(cli and telemetry auto-pull every applicable extension — see below)
--dir=<path> Target directory (default ./<name>)
--help, -h Show help.
--version, -v Show version.Any flag you pass pre-fills the matching prompt; anything you omit falls through to the interactive prompt. Combine with --yes to skip prompts entirely while still overriding individual defaults:
pnpm create @modularityjs my-app --yes --database=prisma --cache=redis --features=events,healthCross-cutting auto-wiring
Two features pull in every applicable extension for the rest of the plan so the scaffolded app is functional end-to-end, not just at the contract level.
Telemetry extensions
Adding telemetry to --features wires the OpenTelemetry tracer and every applicable *-telemetry extension so spans show up for every contract without further configuration:
| Plan element | Extension added |
|---|---|
--http=fastify | http-telemetry |
--logger=console | logger-telemetry |
--cache=... | cache-telemetry |
--session=... | session-telemetry |
--features=events | events-telemetry |
--features=scheduler | scheduler-telemetry + lock-telemetry (scheduler pulls in lock) |
--queue=... | queue-telemetry |
--rate-limit=... | rate-limit-telemetry |
--storage=... | storage-telemetry |
--mail=... | mail-telemetry |
--sms=... | sms-telemetry |
--notification=... | notification-telemetry |
--webhook=... | webhook-telemetry |
--outbox=... | outbox-telemetry |
The shared @modularityjs/plugins package is added once as the AOP host.
CLI extensions
Adding cli to --features wires @modularityjs/cli + @modularityjs/cli-commander and every applicable *-cli extension. The scaffolder emits a cli npm script alongside the HTTP entrypoint (or wires the CLI as the primary dev script when --http=none), so pnpm cli --help (or pnpm dev -- --help for CLI-only apps) lists the admin commands for every contract in your plan:
| Plan element | CLI extension added |
|---|---|
(always — base commands like modules, services) | modularity-cli |
--http=fastify | http-cli |
--cache=... | cache-cli |
--database=... | database-cli |
--features=events | events-cli |
--features=scheduler | scheduler-cli + lock-cli |
--features=health | health-cli |
--auth=jwt | auth-cli |
--authz=... | authz-cli |
--queue=... | queue-cli |
--rate-limit=... | rate-limit-cli |
--storage=... | storage-cli |
--webhook=... | webhook-cli |
--assets=... | assets-cli |
--outbox=... | outbox-cli |
--ws=fastify | ws-cli |
--features=telemetry (only when telemetry extensions fire) | plugins-cli |
To opt out of any auto-added extension, delete the corresponding entry from src/bootstrap.ts after scaffolding (or src/main.ts if neither HTTP nor CLI is selected — that's the only plan that uses main.ts for the modules array). add is additive and won't re-introduce removed modules.
Cross-contract validation
Some flag combinations imply dependencies. The scaffolder fails early with a clear error rather than producing a broken project:
--authz=...requires--auth=....--mfa=...requires--auth=....--ws=fastifyrequires--http=fastify.--outbox=typeormrequires--database=typeorm-sqliteor--database=typeorm-postgres.--outbox=prismarequires--database=prisma.--notification=mailrequires--mail=...;--notification=smsrequires--sms=....
When --features=cli is on, auth-cli, authz-cli, queue-cli, storage-cli and every other applicable *-cli extension auto-attaches. Same for --features=telemetry and the corresponding *-telemetry extensions.
Auth (JWT)
--auth=jwt wires AuthModule + AuthJwtModule.forRoot({ secret }), and — when --http=fastify is also on — the HTTP extensions HttpAuthModule + HttpAuthJwtModule so handlers can read request.identity and use @Authenticated().
The HS256 signing secret (32 random bytes, base64url) is generated at scaffold time and stored in three places:
.env.exampleasJWT_SECRET=...— the canonical override..modularityjs/scaffold.json— soaddre-runs against the same secret deterministically.- A fallback literal inside the
AuthJwtModule.forRoot({...})call rendered intosrc/bootstrap.ts, so the app boots without.envset up.
Production deployments must override JWT_SECRET in the environment. Treat the generated secret as a dev placeholder, not a real secret.
Running add --auth=jwt on a project that already has auth is a no-op for the secret (the existing value is kept). Disabling and re-enabling generates a fresh secret.
What you get
The scaffolder writes a small, opinionated project structure. Some files are always emitted; others depend on the plan.
Always emitted:
package.jsonwith@modularityjs/modularity,@modularityjs/di,@modularityjs/di-inversify, plus the contract + driver pairs you selected, anddev/build/start/testscripts.tsconfig.json,tsconfig.build.json, andeslint.config.mjsmatching the framework's coding standard.src/bootstrap.ts— exports anasync function bootstrap()that returnscreateApp({ di: inversify, modules: [...] })with the modules for your selected contracts already wired. The entrypoint files below import it and pick the rightapp.start(...)call.README.md,.gitignore,.npmrc(setsengine-strict=trueplus the private-registry mapping; the pnpm version itself is pinned viapackageManager: 'pnpm@…'inpackage.json),.prettierrc,.prettierignore,.editorconfig,commitlint.config.mjs,.husky/pre-commit,.husky/commit-msg..github/workflows/ci.yml— runspnpm install --frozen-lockfile,pnpm format:check,pnpm build,pnpm typecheck,pnpm lint,pnpm teston push and PR.AGENTS.md, generated automatically afterpnpm installso coding agents pick up the framework's conventions immediately..modularityjs/scaffold.json— the persisted plan that powers theaddsubcommand.
Entrypoint (exactly one is chosen based on the plan):
src/server.ts— when--http=fastify; callsapp.start({ http: ... }).src/cli.ts— when--features=cli; dispatches to the CLI runner. Emitted alongsideserver.tsif both are on.src/main.ts— only when neither HTTP nor CLI is selected (e.g. a pure worker plan).
When --http=fastify:
src/app.module.ts,src/hello.controller.ts,src/hello.service.ts— a seeded/hello/:nameroute.src/__tests__/hello.service.spec.ts— a passingcreateTestHarnessexample.
When any driver reads runtime env vars (Redis, Postgres, JWT secret, …):
.env.exampleplus--env-file-if-exists=.envin thedev/startscripts.
The output compiles and runs out of the box — pnpm dev starts the app immediately.
add subcommand
Run from inside a scaffolded project to bolt on another contract or middleware without re-running the full scaffolder:
cd my-app
pnpm create @modularityjs add --cache=redisBy default this writes a preview to .modularityjs/preview/ so you can diff against your current code and pick what to apply:
diff -ru . .modularityjs/preview/ | lessPass --apply --force to overwrite the regenerable plan files — package.json, src/bootstrap.ts, src/main.ts, src/server.ts, src/cli.ts, .env.example, and .modularityjs/scaffold.json — in place. Orphan entrypoints (e.g. an src/cli.ts left behind after dropping --features=cli) are removed too. Handcrafted files like app.module.ts and controllers are never touched:
pnpm create @modularityjs add --features=events,scheduler --apply --forceSingle-driver flags replace the existing selection — add accepts every contract in the registry (--cache, --session, --authz, --encryption, --queue, --rate-limit, --storage, --mail, --sms, --webhook, --secrets, --template, --i18n, --feature-flags, --assets, --ws) plus the special-shape flags --auth, --database, --outbox. --http, --validation, --logger are scaffold-only flags — add doesn't take them. Multi-select lists (--middleware, --features, --media, --mfa, --notification) are unioned with what's already in the plan. Run pnpm create @modularityjs add --help for the full list. After applying, re-run pnpm install and optionally regenerate AGENTS.md.
After scaffolding
The scaffolder prints these next steps on completion:
cd my-apppnpm dev— runs the boot script in watch mode (loads.envautomatically when present).curl http://localhost:3000/hello/worldto hit the seeded route.- Read Getting Started for the mental model behind
createApp, modules, and the contract/driver split.