Every file inside a Magento 2 module, mapped and explained.
What is the difference between registration.php and etc/module.xml?
registration.php tells Composer's autoloader where the module lives on disk — it runs at bootstrap and registers the namespace + path with ComponentRegistrar. Without it, Magento literally cannot find your code. etc/module.xml tells Magento's module manager the module exists, what its setup_version is, and its load-order dependencies via <sequence>. You need both: registration.php for the autoloader, module.xml for module:enable. A common bug is a working registration.php with a missing module.xml — the class loads, but DI etc/di.xml is silently ignored because the module is not enabled.
Was this helpful?
Why does my plugin in etc/di.xml not fire on the storefront?
You almost certainly put it in etc/frontend/di.xml when you needed it in etc/di.xml — or vice-versa. Magento has four DI scopes: etc/di.xml (global), etc/frontend/di.xml (storefront only), etc/adminhtml/di.xml (admin only), etc/webapi_rest/di.xml + webapi_soap/di.xml (REST/SOAP). Plugins on storefront-only services (e.g. CustomerSession) live under etc/frontend/. Plugins on universal services (e.g. OrderRepository) live in global etc/di.xml. Run bin/magento setup:di:compile after every move — the compiled interceptors are area-scoped and won't pick up the change otherwise.
Was this helpful?
Should I use db_schema.xml or InstallData / UpgradeData scripts?
db_schema.xml — always, for table structure. The legacy InstallSchema / UpgradeSchema classes are deprecated since 2.3 and removed-in-spirit since 2.4. Declarative schema lets you diff via setup:db-declaration:generate-whitelist, supports rollback, and survives setup:upgrade --safe-mode. For data seeds (config rows, EAV attributes, sample products), use Setup/Patch/Data/<PatchName>.php implementing DataPatchInterface. Patches run once, are tracked in patch_list, and are idempotent if you write them right. Don't mix the two — declarative schema for structure, data patches for content.
Was this helpful?
How do ACL resources in etc/acl.xml actually gate admin access?
An ACL resource is just a string ID like Vendor_Module::manage_widgets. You declare it in etc/acl.xml under the Magento_Backend::admin tree, then reference it in three places: (1) etc/adminhtml/menu.xml — the resource attribute hides the menu item from users without the role; (2) the controller's _isAllowed() method — gates the page itself; (3) etc/adminhtml/system.xml — gates config sections. ACL is permission-only — it doesn't authenticate, it doesn't audit, it just answers yes/no. Always implement _isAllowed(); menu hiding is cosmetic, anyone with the URL can hit the controller without it.
Was this helpful?
Do I need both etc/webapi.xml and etc/schema.graphqls for one feature?
Only if you want both REST and GraphQL clients. The good pattern: build a Service Contract (Api/ + Api/Data/ + Repository) once, then expose it through whichever transport(s) you need. etc/webapi.xml maps an HTTP route + method to a service interface method — pure declarative. etc/schema.graphqls defines a GraphQL type and field, and a Resolver class translates the GraphQL args into a service-contract call. Both transports re-use the same Repository, so business logic lives in one place and you avoid the classic Magento 2 trap of REST and GraphQL drifting out of sync.
Was this helpful?
Request a quote
I'll reply within 2-4 hours business with a written quote and timeline.