Claude, Module Anatomy: 5 questions answered.

Where each Magento 2 module file lives and why: registration.php, module.xml, di.xml, db_schema, ACL, webapi.xml, GraphQL schema, MFTF.

Kishan Savaliya
Kishan Savaliya
Adobe Certified Magento Commerce Developer
Ahmedabad [IN]working hours, replies within four hours
When do you need it

Read personally. Never shared. Or email the brief.

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.

Open this answer on its own page

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.

Open this answer on its own page

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.

Open this answer on its own page

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.

Open this answer on its own page

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.

Open this answer on its own page

Not answered above?

Send the question as a brief; the answer comes back in writing within 24 hours, with a quote if it needs work.