The Directory Structure
What each top-level directory holds: which you edit, which are generated and disposable, and which must never be web-readable.
app/ bin/ dev/ generated/ lib/ pub/ setup/ var/ vendor/A Magento install has nine top-level directories. Three hold work you write, two are disposable, and exactly one may be served by the web server.
| Directory | What is in it |
|---|---|
app/ | Your code in app/code, your themes in app/design, and the two configuration files in app/etc. |
bin/ | bin/magento, the command line entry point. |
dev/ | Test suites and build tooling that ship with the platform. |
generated/ | Generated classes and compiled DI configuration. Disposable. |
lib/ | Framework code and assets not shipped as modules. |
pub/ | The document root: index.php, static/ for theme assets, media/ for uploads. |
setup/ | The installer behind the setup:* commands. |
var/ | Cache, logs, sessions, reports. Disposable. |
vendor/ | Composer packages, including every Magento module. Read it, never edit it. |
Only pub is public
The document root is pub/, not the project root. Everything else, app/etc/env.php above all, must be unreachable over HTTP.
Disposable means disposable
Delete generated/ and var/cache whenever they look stale. Then run bin/magento cache:flush, and in production mode bin/magento setup:di:compile as well, before the next request arrives.
Example
# the directories you touch most, and why
app/code/Vendor/Catalogue/ # your module: PHP, XML, templates, JS
app/design/frontend/Vendor/theme/ # your theme
app/etc/config.php # which modules exist - committed
app/etc/env.php # credentials and mode - not committed
vendor/magento/module-catalog/ # core module - read for reference only
pub/index.php # the only PHP file the web server runs
pub/static/frontend/ # generated CSS, JS and templates
pub/media/catalog/product/ # uploaded images
var/log/system.log # your logger output lands here
var/log/exception.log # uncaught exceptions
var/report/ # one file per error page, named by its ID
generated/code/ # factories, proxies, interceptors
generated/metadata/ # compiled DI configuration
# when something will not take effect, these two are the first suspects
rm -rf generated/code generated/metadata var/cache var/page_cache
bin/magento cache:flushWhen to use it
- Auditing a server where the storefront works but app/etc/env.php is reachable over HTTP.
- Writing a .gitignore and a deployment script that ships app/code but not var/ or generated/.
- Finding the log or report file behind an error page a tester has screenshotted.
More examples
The web server points at pub
Magento ships nginx.conf.sample in the project root; it expects $MAGE_ROOT and sets the root to $MAGE_ROOT/pub itself, along with the rules that keep var/, app/ and generated/ unreachable. Including it is safer than writing your own server block, and it is the fastest fix for a store whose project root is exposed.
# /etc/nginx/sites-enabled/magento.test
upstream fastcgi_backend {
server unix:/run/php/php8.4-fpm.sock;
}
server {
listen 80;
server_name magento.test;
set $MAGE_ROOT /var/www/magento;
set $MAGE_MODE developer;
include /var/www/magento/nginx.conf.sample;
}The module list is a committed file
A module is enabled when its entry is 1 here, so this file - not the presence of the directory - decides what loads. It belongs in version control, which is how a deployment knows to enable your new module; env.php, holding credentials, does not. Changing it takes effect on bin/magento setup:upgrade.
<?php
// app/etc/config.php - written by module:enable, committed to git
return [
'modules' => [
'Magento_Store' => 1,
'Magento_Directory' => 1,
'Vendor_Catalogue' => 1,
'Vendor_Legacy' => 0
]
];Static files are copies, not sources
This explains the most common "my template change did nothing" report. In developer mode pub/static is filled on demand, in production mode only by setup:static-content:deploy, so the same edit behaves differently depending on the mode the store is in.
# edit the source
app/design/frontend/Vendor/theme/Magento_Catalog/templates/product/list.phtml
# .phtml templates are not static view files and are never published to
# pub/static - the storefront reads this file from disk, so what hides the
# edit is the block and layout cache
bin/magento cache:flush block_html layout
# CSS, JS, images and Knockout .html templates are the published copies
pub/static/frontend/Vendor/theme/en_GB/Magento_Catalog/css/...
pub/static/frontend/Vendor/theme/en_GB/Magento_Catalog/template/...
# developer mode: clear the published copies and they regenerate on request
rm -rf pub/static/frontend var/view_preprocessed
# production mode: nothing regenerates on request, publish explicitly
bin/magento setup:static-content:deploy en_GB -f
Discussion