Angeo AEO Audit for Magento 2
Runs a CLI audit that scores how well a store is optimized for AI answer engines (ChatGPT, Gemini, Claude, Perplexity), checking 15 signals and reporting specific fixes to improve AI-driven discovery.
Build Tests
Code Quality
Release Discipline
Release history audited up to v4.2.3
Tested on Magento 2.4.9
Recent Test History
Each release is tested against the latest Magento version at that time.
Top Contributors
View LeaderboardShare This Module's Status
README
Loaded from GitHubAngeo AEO Audit β AI Engine Optimization for Magento 2
One CLI command that tells you exactly why ChatGPT, Gemini, Claude, and Perplexity aren't recommending your store β and how to fix it.
- π Project home: angeo.dev
- π¦ Source: github.com/angeo-dev/module-aeo-audit
- π Issues: github.com/angeo-dev/module-aeo-audit/issues
- π Full guide: Magento 2 AEO Guide 2026
Compatibility
| Component | Version |
|---|---|
| Magento Open Source | 2.4.6, 2.4.7, 2.4.8 |
| Adobe Commerce | 2.4.6, 2.4.7, 2.4.8 |
| Adobe Commerce Cloud | All current versions |
| PHP | 8.1 β 8.5 |
| Themes | Luma, HyvΓ€ |
Tested with: Magento Open Source 2.4.7-p3 + PHP 8.3 + HyvΓ€ 1.3.
What's new in v4.0.0
The evidence layer. Signals 1β15 audit configuration; v4 adds two signals
that audit reality: waf_reality (does the edge actually let the bots in that
robots.txt invites?) and ai_crawler_activity (which AI crawlers demonstrably
visited, from GDPR-safe evidence sources). Plus: purpose-classified bot
grading (blocking GPTBot is a licensing choice, blocking OAI-SearchBot is an
AEO failure), GitHub Actions CI with a Mage-OS installability job, i18n
dictionary, configurable cron schedule, vendor-neutral & switchable fix
hints, and a security fix in the CrUX checker (TLS verification re-enabled,
API key moved from URL to header, encrypted key now actually decrypted).
Full details in CHANGELOG.md.
What's new in v3.0.0
Major release β see CHANGELOG.md for the breaking-change migration guide if you have custom checkers.
20 signals (up from 9), reflecting the actual AEO landscape of 2026: AI shopping integrations, merchant policies, agentic commerce, and structured-data quality.
6 new checkers:
merchant_policiesβMerchantReturnPolicy+OfferShippingDetailsβ required by Google AI Mode and ChatGPT Shopping since Jan 2026organization_schemaβ brand entity in AI knowledge graphsucp_profileβ Universal Commerce Protocol (/.well-known/ucp), with built-in security check that detects leaked JWK private keysjsonld_qualityβ three-page schema breadth audit (homepage / category / product),WebSite+SearchAction,BreadcrumbList,ItemListwell_knownβ discovery matrix for/.well-known/{ucp,ai-plugin.json,security.txt,mcp}core_web_vitalsβ LCP / INP / CLS via Google CrUX API (free, opt-in with API key)
Refactored architecture (this is the BC-break):
- Shared
Service\HttpCacheβ eliminates duplicate fetches across checkers (hundreds of redundant HTTP requests on multi-store audits before, dozens now) Service\StoreUrlSamplerβ single source of truth for product / category / CMS URL sampling- New
--categoryand--fail-on-severityCLI flags for CI workflows - Per-checker exception isolation β slow or failing checkers no longer halt the audit run
Note on access-log monitoring: an
ai_bot_trafficchecker was prototyped during v3 development and excluded from the release after security review β it encouraged broad read access on/var/log/nginx/, didn't work on Cloud/containerised hosting, and was dominated by false positives behind edge caches. AI-bot traffic is better measured at the edge (Fastly/Cloudflare Analytics) or via APM (New Relic, Datadog) than inside a PHP module. See CHANGELOG.md "Considered and rejected" for the full rationale. Thelive_signalcategory remains inCheckerInterfacefor third-party modules with secure live-signal sources β notablyangeo/module-aeo-brand-visibility.
What it checks β 18 signals
Two layers since v4.0.0: configuration signals verify the store is set up for AI engines; the evidence layer verifies AI engines actually reach it.
| # | Signal | Code | Weight | Category | What it validates |
|---|---|---|---|---|---|
| 1 | robots.txt β AI bots | robots_txt |
1.0 | technical | 12 AI bots, syntax errors, versioned UAs, conflicting rules |
| 2 | llms.txt β content map | llms_txt |
1.0 | technical | llmstxt.org v2 structure, store-locale + currency match, cross-host links, empty blockquote, headings before the first H2, HTML links where mirrors exist |
| 3 | llms.jsonl β catalog | llms_jsonl |
0.75 | technical | JSON Lines validity, required fields, eCommerce fields |
| 4 | sitemap.xml | sitemap |
0.8 | technical | XML, lastmod, .gz, catalog disproportion |
| 5 | Product schema | product_schema |
1.0 | technical | JSON-LD on real product, offers, HyvΓ€ detection |
| 6 | Merchant policies β NEW | merchant_policies |
0.9 | technical | hasMerchantReturnPolicy, OfferShippingDetails, priceValidUntil, itemCondition |
| 7 | Organization schema β NEW | organization_schema |
0.8 | technical | Organization / OnlineStore on homepage, sameAs, logo |
| 8 | UCP profile β NEW | ucp_profile |
0.9 | technical | /.well-known/ucp, signing keys, leaked-private-key detection |
| 9 | AI product feed | ai_product_feed |
1.0 | feed | Feed file, /.well-known/ai-plugin.json, REST endpoint |
| 10 | JSON-LD quality β NEW | jsonld_quality |
0.7 | technical | Breadcrumb, ItemList, WebSite+SearchAction, duplicate schemas |
| 11 | Canonical + hreflang | canonical |
0.7 | technical | Canonical agrees with og:url + JSON-LD url; hreflang on multi-store |
| 12 | Open Graph | open_graph |
0.7 | technical | All 5 OG tags, description length |
| 13 | FAQ schema | faq_schema |
0.5 | technical | FAQPage JSON-LD on homepage or sampled CMS page |
| 14 | Well-known matrix β NEW | well_known |
0.5 | technical | ucp / ai-plugin.json / security.txt / mcp inventory |
| 15 | Core Web Vitals β | core_web_vitals |
0.5 | external_api | LCP / INP / CLS via Google CrUX (API key required) |
| 16 | WAF reality check β β | waf_reality |
0.9 | technical | Probes the edge with real AI crawler UAs β flags bots robots.txt allows but the WAF/CDN blocks (challenge pages detected even behind HTTP 200) |
| 17 | AI crawler activity β β | ai_crawler_activity |
0.5 | live_signal | Which AI crawlers actually visited, by class (search / training / fetch), from GDPR-safe evidence sources |
| 18 | A2A Agent Card β β β | agent_card |
0.6 | technical | /.well-known/agent-card.json β required when the UCP profile declares an a2a transport, informational otherwise; flags cards stranded at the pre-0.3 /.well-known/agent.json path |
| 19 | llms.txt v2 link relations β NEW | link_relations |
0.7 | technical | rel="alternate" type="text/markdown" + rel="describedby" in <head> or Link: header; follows both and fails on a declared-but-broken target; Link: header on the mirror; both page.html.md and page.md forms |
| 20 | agents.md β NEW | agents_md |
0.7 | technical | Delivery / returns / privacy actually reachable, linked policies, cross-reference to llms.txt, text/html shadowing, /sitemap_agentic_discovery.xml |
β = added in v3.0.0 Β· β β = added in v4.0.0 Β· β β β = added in v4.1.0.
The agent card is conditional on purpose
Almost no Magento store has opted into A2A, and scoring them all down for a
protocol they never adopted would make the number less useful, not more. So
agent_card asks the UCP profile first. If the store advertises an a2a
transport, the card is part of the contract and its absence is a FAIL. If
it does not, the signal passes with applicable: false in the details.
One case is flagged regardless: a card served only at /.well-known/agent.json,
the path A2A used before 0.3. A spec-compliant 1.0.0 client never looks there,
so the operator believes they have published a card that, to the protocol, does
not exist.
v4 grading philosophy: bots are judged by purpose
The bot catalog (BotRegistry) classifies every
AI agent as training (collects pages to train models), search
(indexes for answer engines β this traffic earns citations), or fetcher
(real-time page access for a user asking right now):
- Blocking a search crawler (OAI-SearchBot, PerplexityBot, Claude-SearchBot) fails the audit β the store disappears from AI answers.
- Blocking a training crawler (GPTBot, ClaudeBot, CCBot, Bytespider) is a respected licensing choice β reported, never punished. Opting out of model training does not remove a store from AI search.
- Opt-out tokens (Google-Extended, Applebot-Extended) never crawl anything themselves and are reported as conscious-choice notices.
The evidence layer
waf_reality and ai_crawler_activity corroborate each other:
| waf_reality | ai_crawler_activity | Diagnosis |
|---|---|---|
| edge blocks probe | zero hits | Real block β allow-list AI crawlers in your WAF/CDN bot management |
| edge blocks probe | healthy hits | Verified-bot program correctly rejecting UA spoofers β no action needed |
| consistent | zero hits | Store likely not yet discovered β work on feeds, llms.txt, citations |
| consistent | search hits | Fully AI-visible β |
Evidence sources for ai_crawler_activity (pluggable via
Angeo\AeoAudit\Api\BotHitSourceInterface):
-
Built-in instrumentation (default, zero-config): a frontend plugin counts AI bot requests at the PHP layer on every hosting model. Limitation reported honestly: requests served entirely from full-page cache never reach PHP and are not counted.
-
Webserver access log (opt-in): set an absolute path under Stores β Configuration β Angeo AEO β Live Signal. Only the trailing 8 MB is read; combined and JSON-lines formats are auto-detected. Never
chmodthe live log β grant targeted read access instead:# point-grant read to the PHP user (survives permission audits): setfacl -m u:www-data:r /var/log/nginx/access.log # β¦or copy bot-filtered lines on rotation (logrotate postrotate): grep -Ei 'GPTBot|OAI-SearchBot|ClaudeBot|Claude-SearchBot|PerplexityBot|Amazonbot|Applebot|meta-externalagent|CCBot|Bytespider' \ /var/log/nginx/access.log.1 > /var/log/ai-bots/access.logBehind a CDN the origin log under-counts (cache hits never reach the origin) β the report says so explicitly.
-
Your own adapter (CDN analytics APIs are the highest-fidelity source β they see cache hits too). Implement
BotHitSourceInterface, register via di.xml on the checker'ssourcesargument.
Privacy: only aggregates β bot code, class, store, date, count β are ever
persisted (angeo_aeo_bot_hit). No IPs, no URLs, no raw user agents, no log
lines. Retention (default 90 days) is enforced by cron. Absence of any
source is an informational note, never a score penalty.
Installation
composer require angeo/module-aeo-audit
bin/magento setup:upgrade
bin/magento cache:flush
For full coverage, install the companion modules:
composer require \
angeo/module-llms-txt \
angeo/module-rich-data \
angeo/module-openai-product-feed \
angeo/module-openai-product-feed-api \
angeo/module-ucp \
angeo/module-aeo-brand-visibility
CLI usage
# Audit all stores
bin/magento angeo:aeo:audit
# Specific store
bin/magento angeo:aeo:audit --store=en_us
# JSON output (for dashboards / CI)
bin/magento angeo:aeo:audit --format=json
# Markdown report to file
bin/magento angeo:aeo:audit --format=markdown --output=/var/www/html/aeo-report.md
# Fast technical-only checks (skip external APIs)
bin/magento angeo:aeo:audit --category=technical
# Run only external-API checks (Core Web Vitals + any third-party live signals)
bin/magento angeo:aeo:audit --category=external_api,live_signal
# Fail build if score below threshold
bin/magento angeo:aeo:audit --fail-on=80
# Fail build if any critical-severity check fails
bin/magento angeo:aeo:audit --fail-on-severity=critical
# Run without saving to DB (CI / read-only environments)
bin/magento angeo:aeo:audit --no-save
Sample output:
AEO Score: [ββββββββββββββββββββ] 81% β Good
β Pass: 12 β Warn: 3 β Fail: 1
Critical fixes needed:
β Install angeo/module-openai-product-feed and register at chatgpt.com/merchants
π‘ Fix with angeo modules:
composer require angeo/module-openai-product-feed angeo/module-openai-product-feed-api
composer require angeo/module-ucp
Configuration
Some checkers need configuration. All are accessed via: Stores β Configuration β Angeo AEO.
| Setting | Purpose |
|---|---|
| CrUX API Key | Required by core_web_vitals checker. Free key from console.cloud.google.com β enable the Chrome UX Report API. Stored encrypted. |
Admin UI
- Marketing β Angeo AEO β AEO Audit Results β full history grid
- Marketing β Angeo AEO β Score Trend β line chart of AEO score over time
- βΆ Run Audit Now button (on the Audit Results grid, Score Trend, and result
view pages) β triggers an on-demand audit. Since 3.1.0 this is a POST action
protected by the admin form key and the dedicated
Angeo_AeoAudit::run_auditACL permission; the old GET menu entry was removed.
Score interpretation
| Score | Label | Typical situation |
|---|---|---|
| 0β25% | Critical | Default Magento install. AI crawlers blocked. No schema. |
| 26β50% | Needs Improvement | Some fixes applied. Feed or merchant policies missing. |
| 51β75% | Needs Improvement | Core signals in place. UCP, ai-plugin.json, or hreflang missing. |
| 76β90% | Good | Strong foundation. Minor gaps in well-known or CWV. |
| 91β100% | Excellent | Full 2026 AEO compliance. |
Cron
Scheduled audit β default every Monday at 03:00 server time, configurable under Stores β Configuration β Angeo AEO β Scheduled Audit since v4.0.0. Results saved to DB (last 50 per store); expired bot-hit counters pruned per the retention setting.
bin/magento cron:run --group=default
For fast daily checks (without external APIs or log scans), schedule an
additional cron job calling the audit with --category=technical.
Extending with custom checks
Implement Angeo\AeoAudit\Api\CheckerInterface (or extend
Angeo\AeoAudit\Model\Checker\AbstractChecker, which provides HTTP cache,
URL sampling and JSON-LD parsing), and register via di.xml:
<type name="Angeo\AeoAudit\Model\AuditRunner">
<arguments>
<argument name="checkers" xsi:type="array">
<item name="my_check" xsi:type="object">Vendor\Module\Model\Checker\MyChecker</item>
</argument>
</arguments>
</type>
v3 interface:
public function getName(): string; // "My Custom Check"
public function getCode(): string; // "my_check"
public function getWeight(): float; // 0.0β1.0
public function getCategory(): string; // CheckerInterface::CATEGORY_*
public function getSeverity(): string; // CheckerInterface::SEVERITY_*
public function getFixCommand(): string; // "composer require vendor/fix-module" or ""
public function check(\Magento\Store\Api\Data\StoreInterface $store): CheckResult;
Migrating from v2? See CHANGELOG.md for the migration guide.
Running tests
vendor/bin/phpunit -c app/code/Angeo/AeoAudit/phpunit.xml
v3 ships with unit tests covering all 15 checkers, both services
(HttpCache, StoreUrlSampler), the AuditRunner, and the report value
objects.
Code quality
# Magento Coding Standard
vendor/bin/phpcs --standard=Magento2 \
--extensions=php,phtml --severity=10 \
app/code/Angeo/AeoAudit/
# PHPStan static analysis
vendor/bin/phpstan analyse -l 5 app/code/Angeo/AeoAudit/
The Angeo AI Visibility Suite
| Module | Signal | Purpose |
|---|---|---|
angeo/module-aeo-audit |
β | This module β audit all 18 signals |
angeo/module-robots-txt-aeo |
#1 | Inject AI bot rules into robots.txt |
angeo/module-llms-txt |
#2, #3 | Generate llms.txt and llms.jsonl |
angeo/module-rich-data |
#5, #6, #7, #13 | Product, Organization, FAQ JSON-LD + merchant policies |
angeo/module-openai-product-feed |
#9 | ACP product feed for ChatGPT Shopping |
angeo/module-openai-product-feed-api |
#9 | REST API β 6 ACP endpoints |
angeo/module-openai-instant-checkout |
β | Agentic Commerce Protocol β instant checkout from ChatGPT |
angeo/module-ucp |
#8 | Universal Commerce Protocol β /.well-known/ucp |
angeo/module-aeo-brand-visibility |
(extends) | Live AI visibility across ChatGPT, Claude, Perplexity, Gemini, Groq |
Contributing
Issues and PRs welcome at github.com/angeo-dev/module-aeo-audit.
Before opening a PR:
- Run
vendor/bin/phpunit -c phpunit.xmlβ all tests must pass - Run
vendor/bin/phpcs --standard=Magento2β no MCS violations - Add tests for any new checker
License
MIT β see LICENSE
Made with care by Ievgenii Gryshkun β open-source contributions to the Magento + AI commerce ecosystem.
This content is fetched directly from the module's GitHub repository. We are not the authors of this content and take no responsibility for its accuracy, completeness, or any consequences arising from its use.