Skip to content

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.

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() — 2-/3-arg where is injection-safe (values auto-quoted); 1-arg where is raw SQL. 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 4.0.x line kept shipping. The highlights you’ll actually reach for:

  • Storage. Local and S3 file storage behind one interface — named disks, put/get/exists/delete/url/signedUrl, and from-scratch SigV4 signing with no AWS SDK. → Storage
  • Password hashing, solved. wheels.auth.PasswordHasher (PBKDF2, 600k iterations) plus bcrypt via bcryptHash() / bcryptVerify() / bcryptNeedsRehash() — bundled jBCrypt on JVM engines with a pure-CFML fallback, no more hand-rolled salted SHA-256. → Authentication Patterns
  • Authorization policies. wheels.Policy with authorize() / can() / policyScope() and a wheels generate policy generator. → Authorization Policies
  • enableSession(). One-line session-auth wiring in config/services.cfm. → Authentication Patterns
  • Route binding on any column. bindBy="slug" resolves a resource’s :key segment through the dynamic finder. → Route Model Binding
  • toFactory(). Register a DI binding with a closure that builds the instance. → Dependency Injection Usage
  • A sharper CLI. wheels migrate diff, wheels generate --dry-run, a global --offline flag, and wheels coverage for CRAP-ranked change risk. → Database, Code Quality
  • Fail-closed mass assignment. set(massAssignmentStrict=true) rejects posted properties a model didn’t explicitly allow. → Security Hardening

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:

  1. CommandBox keeps working — supported as a server manager and package fetcher (box install wheels-base-template, box server start), indefinitely.
  2. 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.
  3. 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.

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.