Upgrading
What's New in 4.0
You know Wheels. You have a 3.x app in production. This page is the ten-minute answer to "what does 4.0 actually give me?" — features first, tooling second, and the pointers into the upgrade path when you're convinced.
Headline features
Section titled “Headline features”Middleware pipeline. Cross-cutting request handling at the dispatch level — global via set(middleware=[...]) or scoped to route groups. Ships with RequestId, SecurityHeaders, Cors, and RateLimiter (fixed-window, sliding-window, or token-bucket; memory or database storage). → Middleware Pipeline
Built-in dependency injection. Register services in config/services.cfm, resolve with service() or inject(), scope as transient, singleton, or request. This is the change that removed the WireBox dependency — the container is vendor/wheels/Injector.cfc, part of the framework. → The Dependency Injection Container
Chainable query builder, scopes, and enums. model("User").where("status","active").whereNotNull("emailVerifiedAt").orderBy("name").get() — injection-safe, values auto-quoted. Named scopes compose (model("User").active().recent().findAll()), and enum() generates checkers and scopes from a property definition. → Query Builder and Scopes
Background jobs. app/jobs/ CFCs extending wheels.Job, with queues, delayed/scheduled enqueue, retries with exponential backoff, and a worker loop (wheels jobs work). The wheels_jobs table auto-creates on first use. → Background Jobs
Packages. The successor to the legacy plugin system: modules installed into vendor/<name>/, auto-discovered from their package.json, with per-package error isolation. First-party packages include wheels-sentry, wheels-hotwire, wheels-basecoat, wheels-i18n, and wheels-legacy-adapter (3.x shims for a staged migration). → Packages
Auth strategies. Session, token, and JWT authentication ship in the framework (wheels.auth.SessionStrategy, TokenStrategy, JwtService) instead of every app hand-rolling them. → Authentication Patterns
Real-time responses. Server-Sent Events helpers (renderSSE(), streaming writers) and channels for pub/sub. → Server-Sent Events, Channels
Route model binding. .resources(name="users", binding=true) resolves params.key into params.user before the action runs, 404ing on missing records so your actions don't repeat the lookup. → Route Model Binding
WheelsTest. A BDD test framework (describe/it/expect) built into the framework — the TestBox dependency is gone, and browser testing via Playwright is one wheels browser setup away. → Testing
wheels deploy. A Kamal-inspired zero-downtime deployer built into the CLI — deploy.yml, Docker builds, TLS-terminating proxy, rollbacks. Optional: the self-managed Docker, VM, and IIS paths are documented as first-class too. → Deployment & Operations
Smaller but daily-life: composable pagination (paginationNav() with Bootstrap/Tailwind presets), idempotent seeding (seedOnce()), migration reconciliation for shared dev databases (migrate doctor/forget/pretend), HTML5 form helpers, and a rebuilt debug bar.
The tooling story, honestly
Section titled “The tooling story, honestly”4.0 ships a first-party CLI — wheels new, generators, migrate, test, console, deploy — built on the LuCLI runtime and installed via Homebrew, Scoop, or apt/yum. Three sentences for the teams watching this warily:
- CommandBox keeps working — supported as a server manager and package fetcher (
box install wheels-base-template,box server start), indefinitely. - No tooling is required at all — the framework has zero external dependencies in 4.0, installs from two zips, and every CLI task has an in-framework equivalent.
- The CLI is an accelerator, not a gate — everything it does goes through surfaces your app exposes anyway.
Pick your path in one page: Choosing Your Tooling.
Breaking changes, in one breath
Section titled “Breaking changes, in one breath”Eleven, all enumerated with remediations in the upgrade guide. The ones most likely to touch a 3.x app: CORS defaults flipped from wildcard to deny-all, application.wirebox became application.wheelsdi, wheels.Test became wheels.WheelsTest, tests/specs/functions/ became tests/specs/functional/, and production hardening (HSTS, CSRF key, environment-switch lockdown) is on by default. The wheels-legacy-adapter package shims most 3.x patterns while you migrate.