Skip to content

Start Here

Anatomy of a Wheels App

This page is the map of an app created with wheels new: every file and folder it creates, and what each is for. If you’re coming from Wheels 2.x, the biggest change is that the framework moved from a wheels/ folder at the root into vendor/wheels/, and the web root moved to public/.

myapp/
app/ Your application code
controllers/ Controllers (plural PascalCase: Users.cfc)
Controller.cfc Base class your controllers extend
Main.cfc The welcome page's controller
Up.cfc Health check at /up (used by `wheels deploy`)
models/ Models (singular PascalCase: User.cfc)
Model.cfc Base class your models extend
views/ One folder per controller
layout.cfm The default layout
helpers.cfm View helpers available in every view
main/index.cfm The welcome page
events/ Application and request lifecycle hooks (onapplicationstart.cfm, onerror.cfm, …)
global/functions.cfm Global user-defined functions
jobs/ Background jobs (extend wheels.Job)
lib/ Plain CFCs: services, value objects, API clients
mailers/ Mailers
middleware/ Your middleware components
migrator/migrations/ Database migration CFCs
policies/Policy.cfc Base class for authorization policies
snippets/dbmigrate/ Templates the development migrator UI writes migrations from
config/ Configuration, loaded at application start
app.cfm Application.cfc-level settings (this.name, datasources, …)
environment.cfm Which environment this deployment runs as
settings.cfm Shared settings: datasource, reload password, URL rewriting, …
routes.cfm Your routes (mapper() … .end())
services.cfm Dependency-injection registrations
development/ Per-environment overrides: settings.cfm in each,
testing/ read after config/settings.cfm, only in that environment
production/
maintenance/
db/ SQLite database files (development.sqlite, test.sqlite)
plugins/ Legacy 3.x plugin drop-in (deprecated; use packages)
public/ THE WEB ROOT: the only folder a browser can reach
Application.cfc Bootstraps the app: defines mappings, loads .env
index.cfm Single entry point; hands every request to the dispatcher
urlrewrite.xml Rewrite rules for Tuckey
stylesheets/, javascripts/, images/, files/, miscellaneous/
tests/ Your test suite
specs/ models/, controllers/, functional/
runner.cfm The app's test runner
populate.cfm Test-database setup
vendor/ Third-party code; you don't edit anything in here
wheels/ THE FRAMEWORK ITSELF
<package>/ Packages added with `wheels packages add`
.env Environment variables and secrets (never commit)
.env.example The same keys with blank values (commit this)
.gitignore
lucee.json Server configuration for `wheels start`
rewrite.config URL-rewrite rules for `wheels start`
AGENTS.md, CLAUDE.md, .ai/ Docs for AI coding agents working in this app

vendor/ is committed, framework included, so a clone, a CI run or an image build has everything it needs.

There is no magic registry and no generated wiring. public/Application.cfc defines six CFML mappings, all built from the project root one level above the public/ folder:

this.wheels.projectRoot = REReplace(this.wheels.rootPath, "[^/\\]+[/\\]$", "");
this.mappings["/app"] = this.wheels.projectRoot & "app/";
this.mappings["/vendor"] = this.wheels.projectRoot & "vendor/";
this.mappings["/wheels"] = this.wheels.projectRoot & "vendor/wheels/";
this.mappings["/tests"] = this.wheels.projectRoot & "tests";
this.mappings["/config"] = this.wheels.projectRoot & "config";
this.mappings["/plugins"] = this.wheels.projectRoot & "plugins";

this.wheels.rootPath is the directory of Application.cfc itself, so the mappings are the same whichever page under public/ was requested.

That’s the whole contract. Point any CFML-capable web server at public/ and the app can find its own code, no matter where on disk the folder sits or what tool put it there. It also means everything outside public/ — your models, your config, your .env, the framework itself — is unreachable from a browser.

  • app/ — everything you write. Controllers extend Controller, models extend Model, views execute in the controller’s variable scope. Conventions (naming, pluralization) are covered in Conventions over Configuration.
  • config/ — read once at application start. Changing anything here requires a reload (restart the app, or ?reload=true&password=… — see Working Without the CLI).
  • public/ — the web root and only the web root. Static assets are served directly; everything else routes through index.cfm to the dispatcher.
  • vendor/ — the framework plus any installed packages. Commit it. Treat it as read-only: upgrades replace vendor/wheels/ wholesale, so local edits there are lost by design.
  • tests/ — your app’s test suite, run via wheels test, or without the CLI at /wheels/app/tests in development. See Testing.
  • db/ — only meaningful for SQLite setups; other databases live wherever your server keeps them. The .sqlite files are gitignored.
  • plugins/ — where 3.x plugins still load from. Deprecated: new code goes in app/lib/ or a package.
  • lucee.json / rewrite.config — the server and rewrite config wheels start uses. Another server setup ignores them.

Inside vendor/wheels/ two things deserve a mention even though you never edit them:

  • vendor/wheels/Injector.cfc — the built-in dependency-injection container. Wheels 4 does not depend on WireBox; if you remember downloading WireBox for Wheels 3, that requirement is gone.
  • vendor/wheels/wheelstest/ — the built-in test framework (wheels.WheelsTest). Wheels 4 does not depend on TestBox either; the TestBox-and-four-hidden-modules scavenger hunt from the 3.0 era is over.

Both facts matter to one specific reader: the person assembling an app from zips. The complete dependency list for a Wheels 4 app is: the framework. See Manual Installation.