The Directory Structure

What each top-level directory holds: which you edit, which are generated and disposable, and which must never be web-readable.

Syntaxapp/ 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.

DirectoryWhat 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

Example · bash
# 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:flush

When 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.

Example · nginx
# /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.

Example · php
<?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.

Example · bash
# 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

  • Be the first to comment on this lesson.