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 plugins/ Legacy plugin folder (deprecated; use packages) migrator/migrations/ Database migration CFCs policies/Policy.cfc Base class for authorization policies snippets/ Templates the generators write code 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 db/ SQLite database files (development.sqlite, test.sqlite) 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) .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 appThe generated .gitignore excludes vendor/. Per-environment overrides go in config/<environment>/settings.cfm; wheels new doesn’t create those folders, so add one when you need it.
How the pieces find each other
Section titled “How the pieces find each other”There is no magic registry and no generated wiring. public/Application.cfc defines six CFML mappings, all relative to the public/ folder:
this.mappings["/app"] = expandPath("../app/");this.mappings["/vendor"] = expandPath("../vendor/");this.mappings["/wheels"] = expandPath("../vendor/wheels/");this.mappings["/tests"] = expandPath("../tests");this.mappings["/config"] = expandPath("../config");this.mappings["/plugins"] = expandPath("../plugins");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.
What each top-level folder is for
Section titled “What each top-level folder is for”app/— everything you write. Controllers extendController, models extendModel, 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 throughindex.cfmto the dispatcher.vendor/— the framework plus any installed packages. Treat it as read-only: upgrades replacevendor/wheels/wholesale, so local edits there are lost by design.tests/— your app’s test suite, run viawheels test, or without the CLI at/wheels/app/testsin development. See Testing.db/— only meaningful for SQLite setups; other databases live wherever your server keeps them. The.sqlitefiles are gitignored.lucee.json/rewrite.config— the server and rewrite configwheels startuses. Another server setup ignores them.
Framework internals worth knowing about
Section titled “Framework internals worth knowing about”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.