Mago 1.47 adds an Extension API for custom lint rules and analyzer plugins. Extensions can be written in PHP or any language that supports Mago's worker protocol.
Mago is a PHP linter, formatter, and static analyzer written in Rust by Carthage Software. Version 1.47.0 was released on August 18, 2026. CHECK24 funded the Extension API and PHP SDK.
The main changes include:
- A language-neutral worker protocol for extensions
- A bundled PHP SDK
- Custom linter rules and analyzer plugins
- CLI commands for validating and listing extensions
- New analyzer checks and fixes
Extensions Run as Separate Programs
Mago does not load PHP inside its Rust process. Instead, it starts an extension as a separate worker program and communicates with it through a framed binary protocol.
One extension host can run a pool of worker processes. This lets Mago spread work across CPU cores while keeping the extension outside the main process.
The protocol is language-neutral, so an extension does not have to use PHP. Mago ships a first-party PHP SDK in the carthage-software/mago Composer package, which is the quickest path for most PHP developers.
Extension workers are not sandboxed. They run with the same operating-system permissions and environment given to the host, so projects should only install extensions they trust.
Writing a Lint Rule in PHP
A PHP lint rule implements Mago's Rule interface. It declares which syntax nodes it wants to inspect and reports issues when it finds a match:
namespace Acme\Mago\Linter\Rules; use Mago\Sdk\Linter\LintContext; use Mago\Sdk\Linter\Rule; use Mago\Sdk\Linter\RuleDefinition; use Mago\Sdk\Reporting\Issue; use Mago\Sdk\Reporting\Level; use Mago\Sdk\Syntax\NodeKind; final class NoEvalRule implements Rule { public function getDefinition(): RuleDefinition { return new RuleDefinition( code: 'acme/no-eval', name: 'No eval', description: 'Disallows evaluating dynamically generated PHP code.', defaultLevel: Level::Error, defaultEnabled: true, targets: [NodeKind::EvalConstruct], ); } public function lint(LintContext $context): void { $context->report(Issue::new( 'Avoid evaluating dynamically generated PHP code.', $context->node->span, )); } }
The targets list matters for performance. Mago finds matching nodes in Rust and sends only those syntax trees to PHP. The example rule does not receive unrelated code.
Lint Rules and Analyzer Plugins
Lint rules inspect syntax and report a problem or suggested edit. They work well for project coding rules and checks tied to a known code pattern.
Analyzer plugins can provide deeper information, including:
- Types and callable signatures
- Flow assertions
- Framework entry points
- Property initialization details
- Codebase scans and analysis hooks
This gives framework and package authors a way to teach Mago about behavior that cannot be found from syntax alone.
The formatter and guard do not support extensions in this release.
Registering an Extension
The PHP SDK groups rules and analyzer plugins into an Extension. A small worker script starts it:
use Acme\Mago\AcmeExtension; use Mago\Sdk\Worker; require dirname(__DIR__) . '/vendor/autoload.php'; (new Worker( AcmeExtension::create(), ))->run();
The host is then registered in mago.toml:
[extension-hosts.acme] command = ["php", ".mago/acme-worker.php"]
These commands validate the host, show its registered extensions, and run one rule:
mago extension validate mago extension list mago lint --only acme/no-eval
Carthage Software also provides a Mago extension template with linter, analyzer, test, configuration, and CI examples.
Other Changes in Mago 1.47
The analyzer gains a --skip-ignores option for running without configured suppressions. It can also narrow an array key after a successful array_key_exists() check.
The release fixes several analysis problems involving logical assignments, nullable types, match expressions, private trait methods, missing generic arguments, object shapes, and @var annotations on global statements.
Upgrade Notes
The release notes do not list breaking changes for normal Mago users. Extension authors should depend on the same Mago package version used by the project because the Composer package includes the matching SDK.
Read the extension documentation for the architecture and API reference. The Mago 1.47 release notes contain the complete list of analyzer and linter fixes.