magento/magento-coding-standard
Magento Coding Standard provides PHP_CodeSniffer rules for checking Magento 2 code against official coding standards. It helps developers detect violations, improve code quality, and integrate automated checks into CI pipelines for modules and extensions.
Install as a dev-only dependency so it never ships inside your module's vendor:
composer require --dev magento/magento-coding-standard
Composer 2.2+ will prompt to allow the plugin that registers the standard. If you answered no (or CI runs non-interactively), add to the root composer.json and reinstall:
"config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true } }
Verify registration — you should see Magento2 in the list:
vendor/bin/phpcs -i
First real run, against a single custom module (never the whole Magento root):
vendor/bin/phpcs --standard=Magento2 --extensions=php,phtml -s app/code/Vendor/Module
-s prints the sniff code next to each message (Magento2.Security.XssTemplate.FoundUnescaped, etc.). Always use it — the code is what you need for ignores and for targeted re-runs.
Where to look first:
vendor/magento/magento-coding-standard/Magento2/ruleset.xml — the authoritative list of included rules, severities, and default exclusions. Read this before arguing with a message.vendor/magento/magento-coding-standard/Magento2/Sniffs/ — one directory per category (Security, SQL, PHP, Classes, Functions, Exceptions, …). The sniff source is the fastest way to understand why something is flagged.phpcs.xml once you have more than one module.The first use case is almost always the same: you're preparing a module for Magento Marketplace submission, and the Marketplace technical review runs this exact standard. Running it locally is a cheap proxy for that review — it catches unescaped output, raw SQL, direct ObjectManager usage, missing declare(strict_types=1), discouraged functions, and API-breaking class design long before submission.
Project ruleset instead of CLI flags. Commit a phpcs.xml at the Magento root so every developer and CI job gets identical behaviour with a bare vendor/bin/phpcs:
<?xml version="1.0"?>
<ruleset name="Project">
<rule ref="Magento2"/>
<file>app/code</file>
<exclude-pattern>*/Test/*</exclude-pattern>
<exclude-pattern>app/code/*/*/Setup/*</exclude-pattern>
<arg name="extensions" value="php,phtml"/>
<arg name="parallel" value="8"/>
<arg name="cache" value="var/phpcs.cache"/>
</ruleset>
Excluding Setup/ install scripts is a common relief valve — data patches legitimately use raw SQL and direct DB access, and the sniffs don't model that context.
Composer scripts for the two commands people actually run:
"scripts": {
"cs": "phpcs --standard=phpcs.xml app/code/Vendor",
"cs:fix": "phpcbf --standard=phpcs.xml app/code/Vendor"
}
Changed-files-only in CI/pre-commit. On a large module the full run is slow; scope to the diff:
git diff --name-only --diff-filter=ACM origin/main... | grep -E '\.(php|phtml)$' \
| xargs -r vendor/bin/phpcs --standard=Magento2 -s
CI annotations. --report=checkstyle piped through cs2pr (GitHub Actions) turns findings into inline PR comments, which is far more effective than a log dump people ignore.
Layered quality gate. Run phpcs (style/security patterns) alongside PHPStan with the Magento extension (type correctness) — they catch disjoint classes of bugs. Don't try to make phpcs do static analysis.
Targeted sniff invocations when you're auditing one concern across a huge legacy codebase:
vendor/bin/phpcs --standard=Magento2 --sniffs=Magento2.Security.XssTemplate,Magento2.SQL.RawQuery app/code
IDE wiring. Point PhpStorm (Settings → PHP → Quality Tools → PHP_CodeSniffer) or the VSCode phpcs extension at vendor/bin/phpcs with standard Magento2, and set the "tool path" to the project binary — not a global one. This is where the standard earns its keep day-to-day: errors appear as you type rather than in a Friday afternoon CI failure.
Triage order. Fix Magento2.Security.*, Magento2.SQL.*, and Magento2.PHP.DiscouragedFunction first — those are the ones that block Marketplace approval and represent real risk. Whitespace/formatting sniffs can wait for phpcbf.
allow-plugins and run composer install again; phpcs -i is the diagnostic.phpcs -n (and --warning-severity=0) will hide them from you. Never use those flags when preparing a submission. If a run looks suspiciously clean, drop the flags and re-check.composer.lock and, if a submission failed on a rule you can't reproduce, check out the version the review used before debugging your code.--baseline mechanism. On legacy code you have three options: inline phpcs:ignore annotations, exclude patterns per directory, or a generated ruleset that excludes specific sniffs. The third is the cleanest but drifts silently — review it quarterly.// phpcs:ignore Magento2.Security.XssTemplate.FoundUnescaped — the generic form without a code suppresses everything on that line and is a review smell. The comment must be on the same line or the line immediately above the offending line; phpcs:disable/phpcs:enable pairs scope to a region. Add a short reason after -- in every ignore.phpcbf fixes little. It handles whitespace, alignment, short-array syntax and similar formatting sniffs. Security, SQL, and class-design sniffs are not auto-fixable. Don't promise a "run the fixer" pass as remediation.--parallel and --cache, and raise memory with -d memory_limit=-1 for large runs. Exclude generated/, var/, pub/static/, and vendor/ unconditionally — scanning generated code produces thousands of meaningless findings and can take minutes.--no-dev break the cs job. The standard lives in require-dev; either run the CS job with dev dependencies installed or add a dedicated job/step that installs them.squizlabs/php_codesniffer; if your project or another dev tool pins a conflicting major, Composer resolution fails or sniffs misbehave. Pin PHPCS explicitly in require-dev at a version the standard's composer.json supports, and treat a PHPCS major upgrade as a separate, isolated PR..phtml noise is real. Magento2.Security.XssTemplate produces a lot of hits on legacy templates because it can't always prove an escape happened upstream. Prefer refactoring to escape at the point of output over blanket ignores; when you must ignore, scope it to a single line with a reason.<rule ref="..."><severity>0</severity></rule> — useful for a rule your team has consciously decided against. Document the decision in the ruleset; otherwise the next developer reinstalls the default and "fixes" everything back.<rule ref="Magento2"/> plus your own sniffs, and register it in the same Composer plugin flow. This is how teams add project-specific bans (e.g. no curl_*, no serialize() in new code) without forking the Magento standard.require-dev. The license is OSL-3.0 (a copyleft-flavoured license with source-availability obligations); a dev-only tool that never redistributes with your module keeps that a non-issue, but don't let it leak into a shipped vendor directory or a packaged artifact.How can I help you explore Laravel packages today?