What Magento setup:di:compile Actually Does (In Depth)
setup:di:compile pre-generates the factories, proxies and interceptors Magento would otherwise build at runtime. Here is exactly what it generates, the nine operations it runs, and why production cannot skip it.
Magento is built on dependency injection: classes ask for their collaborators in the constructor and the framework wires them together from di.xml. To make that fast in production, Magento compiles the wiring ahead of time instead of reflecting on every request.
That is what setup:di:compile does. This guide explains exactly what it generates, the nine operations behind the progress bar, why production cannot run without it, and how to read the errors when it fails.
The short answer
setup:di:compile reads every module’s dependency-injection configuration and pre-generates the code Magento would otherwise create at runtime: factories for objects you new, proxies for lazy-loaded dependencies, and interceptors that weave in plugins. It then aggregates all di.xml into compiled, per-area metadata. Everything lands in the generated/ directory. In production mode this step is mandatory; skip it and you get fatal “class not found” errors for the missing generated classes.
What it actually generates
Three kinds of class are created on demand by Magento and pre-built by the compiler:
- Factories (
Panth\Example\Model\ItemFactory) — Magento never injects “newable” objects (entities, DTOs) directly; it injects a generated factory whosecreate()returns a fresh instance. - Proxies (
Panth\Example\Model\Heavy\Proxy) — lazy wrappers declared indi.xml. The real object is only built the first time a method is called, so expensive dependencies do not slow down construction. - Interceptors (
Panth\Example\Model\Item\Interceptor) — for every class that has a plugin (abefore/around/aftermethod), Magento generates an interceptor subclass that runs the plugin chain. No plugins, no interceptor.
It also generates repository and extension-attribute classes (the *ExtensionInterface / *Extension objects behind getExtensionAttributes()). All of this is written to generated/code/; the compiled DI configuration goes to generated/metadata/.
The nine operations behind the progress bar
When you run the command you see a progress bar stepping through roughly nine operations. In plain terms they are:
| Operation | What it does |
|---|---|
| Repositories code generation | Builds repository classes for entities |
| Application code generator | Generates factories, proxies and other newables |
| Interceptors generation | Generates an interceptor per plugged class |
| Area configuration aggregation | Merges all di.xml per area (global, frontend, adminhtml, crontab…) |
| Interception cache generation | Compiles which classes are intercepted and by what |
| Application action list generation | Maps controller action classes for routing |
| Plugin list generation | Compiles the ordered plugin chains |
| Service data attributes generation | Builds extension-attribute classes |
| DI argument compilation | Freezes constructor signatures so no runtime reflection is needed |
Behind the scenes: lazy generation vs full compilation
The clearest way to see what the compiler does is to count the generated classes before and after. On this store in developer mode — where classes are generated lazily, only the first time each is actually used — the generated/code directory held just 141 files (64 factories, 17 proxies, 58 interceptors). Running setup:di:compile rebuilds that directory completely and ahead of any request:
That jump — from 141 to 6,682 generated files — is the point of the command: production pre-builds every factory, proxy, interceptor and repository the application could ever need, because it cannot create them at runtime. The progress bar steps through the nine operations in this order before reporting success:
Compilation was started.
1/9 Repositories code generation
2/9 Proxies code generation
3/9 Application code generation (factories & other newables)
4/9 Interceptors generation
5/9 Area configuration aggregation
6/9 Interception cache generation
7/9 Application action list generation
8/9 Plugin list generation
9/9 Service data attributes generation
Generated code and dependency injection configuration successfully.
Why production mode cannot skip it
The difference between modes is when code is generated. In developer mode, the ObjectManager generates a missing factory, proxy, or interceptor the first time it is needed and caches it in generated/ — slow on first hit, but automatic. In production mode, runtime generation is disabled for performance and security, so every generated class must already exist. If you deploy code that adds a plugin or a new factory and forget to compile, production throws a fatal error because the interceptor or factory class is simply not there.
What setup:di:compile does NOT do
- It does not run schema or data changes — that is setup:upgrade.
- It does not build CSS/JS or anything browsers download — that is setup:static-content:deploy.
- It does not reindex, and it does not flush the full cache (though it clears
generated/before recompiling).
The deploy sequence and --keep-generated
On a production deploy, compile fits between the database step and the static-asset step:
bin/magento maintenance:enable
bin/magento setup:upgrade --keep-generated
bin/magento setup:di:compile
bin/magento setup:static-content:deploy -f en_US
bin/magento cache:flush
bin/magento maintenance:disable
The --keep-generated flag on setup:upgrade matters here: by default the upgrade wipes generated/. If you compile first in a build step (the recommended build-then-deploy pattern), pass --keep-generated so the upgrade does not throw away the freshly compiled code.
Common di:compile errors and how to read them
Circular dependency. Two classes depend on each other through their constructors. The compiler refuses to build it. Break the cycle by injecting a Proxy of one dependency (declared in di.xml) so it is resolved lazily.
Proxy.php: No such file or directory. The generated/ tree is half-built and the optimized Composer autoloader references a class that is not there. Recover with a two-pass autoload: composer dump-autoload → setup:di:compile → composer dump-autoload --optimize.
“Class does not exist” / incompatible argument type. A di.xml preference or constructor argument points at a class or interface that cannot be resolved — usually a typo, a missing module dependency, or an interface with no <preference>.
A compile that fails on a circular dependency, or a production deploy throwing “class not found”? I debug Magento 2 & Adobe Commerce DI, plugins, and deployments end to end — fixed-fee from $499 audit · $2,499 sprint · ~Nh @ $25/hr. See Magento upgrade & deployment service.
Get your build unblockedFrequently asked questions
What does bin/magento setup:di:compile do?
It pre-generates the classes Magento normally creates at runtime — factories, proxies, interceptors (plugins), repositories and extension-attribute classes — and compiles all di.xml into per-area configuration under generated/. It does not touch the database or deploy static files.
Do I need di:compile in developer mode?
No. In developer mode the ObjectManager generates missing factories, proxies and interceptors on first use and caches them. You only need to compile in production mode (or default mode if you want the speed-up).
What is the difference between setup:upgrade and setup:di:compile?
setup:upgrade reconciles the database with your code (schema, patches, module registry). setup:di:compile generates PHP code from your dependency-injection config. They are independent steps; a full production deploy runs upgrade, then compile, then static-content deploy.
What are interceptors, proxies and factories?
Interceptors are generated subclasses that run a class’s plugin chain (before/around/after). Proxies are lazy wrappers so an expensive dependency is only built when first used. Factories return fresh instances of “newable” objects you should not inject directly.
Where does di:compile write its output?
To the generated/ directory: generated classes in generated/code/ and compiled DI metadata in generated/metadata/. (On Magento 2.0–2.1 this was var/generation and var/di.)
Why does di:compile fail with a circular dependency error?
Two classes depend on each other through their constructors, so neither can be built first. Resolve it by injecting a Proxy of one dependency in di.xml, which defers its construction until first use and breaks the cycle.
Does setup:di:compile deploy CSS and JavaScript?
No. Front-end assets are handled by setup:static-content:deploy. The compiler only generates PHP classes and DI metadata.
How long does di:compile take and can I speed it up?
It typically takes one to a few minutes depending on the number of modules and plugins. It is CPU-bound; the main lever is running it on a faster build machine and keeping module count reasonable. Compile once in a build step and ship the generated/ directory to production.
How do I fix a Proxy.php “No such file or directory” error during compile?
The generated/ tree is inconsistent with the optimized autoloader. Run a two-pass autoload: composer dump-autoload (non-optimized), then setup:di:compile, then composer dump-autoload --optimize.