angeo / module-aeo-brand-visibility
Live AI brand visibility audit for Magento 2. Queries ChatGPT, Claude, Perplexity, Gemini and Groq with brand-probing prompts and scores real-world AI recall, citation rate and recommendation presence. Extends angeo/module-aeo-audit v3 via CheckerInterface as the 16th signal, alongside the 15 built-in technical checks.
Angeo AEO Brand Visibility for Magento 2
Measure whether AI assistants actually name your store when a shopper asks them
where to buy something.
The module asks ChatGPT, Claude, Perplexity, Gemini and Groq a set of shopping
questions, reads their answers, and scores how visible your brand is. It repeats
every question several times, so the score comes with a confidence interval
instead of pretending one sample is the truth.
It plugs into angeo/module-aeo-audit
as a live_signal checker, and also runs on its own from the admin panel, cron
or the command line.
Why this exists
Classic SEO tools tell you where you rank on a results page. They tell you
nothing about what a language model says when someone asks it a question. This
module measures that directly: it asks, then reads the answer.
What is measured
Each answer is scored on five signals:
| Signal | Question it answers | Default weight |
|---|---|---|
| Mentioned | Does the answer name the brand at all? | 1.0 |
| Recommended | Is the brand suggested, not just listed? | 1.5 |
| URL cited | Is your domain quoted? | 1.5 |
| First position | Is the brand named before its competitors? | 2.0 |
| Positive tone | Is the wording around the brand favourable? | 0.5 |
A sixth signal, negative tone, is not weighted. It reduces the earned score
by the configured penalty, because "avoid this shop" is worse than not being
mentioned at all.
On top of the per-answer score the module reports:
- Share of voice — your weight against every tracked competitor in the same
answer, based on how often you are named and how early. - Win rate — the share of answers where you are named before every
competitor. - Accuracy issues — answers where a model attributes a website you do not
own to your brand.
Reading the score honestly
Two things decide whether the number means anything.
Grounding. A model answering from memory reflects a training set that is
months old. Nothing you change on your store will move that number this quarter.
A grounded model searches the live web before answering, so it responds to your
work within days. Perplexity is always grounded; ChatGPT, Claude and Gemini can
be, per provider, in the configuration. Groq cannot. If you want a metric that
reacts to what you do, enable grounding.
Sampling. Language models are not deterministic. The same prompt returns
different answers. Every provider and prompt pair is therefore asked N times
(three by default) and reported as a mean with the half-width of its 95%
confidence interval — the ± figure next to the score. A movement smaller than
that margin is noise, and the alert system will not fire on it.
Requirements
- Magento Open Source or Adobe Commerce 2.4.6 – 2.4.8
- PHP 8.2, 8.3 or 8.4
angeo/module-aeo-audit3.x or 4.x- At least one AI provider API key
- A running message queue consumer (or inline run mode, see below)
Installation
composer require angeo/module-aeo-brand-visibility
bin/magento module:enable Angeo_AeoBrandVisibility
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento cache:flush
Start the consumer that executes queued audits:
bin/magento queue:consumers:start angeoBrandVisAuditRun
On most installations consumers_runner in app/etc/env.php starts it from
cron automatically. If your platform does not run message queue consumers at
all, set Run Mode to Inline in the configuration; be aware that a full run
then happens inside the admin request and may hit the PHP or gateway timeout.
Configuration
Stores → Configuration → Angeo AEO → Brand Visibility (AI Models)
Almost every setting is available per website and per store view, so a
multi-store installation can measure each brand separately.
General
| Field | Notes |
|---|---|
| Enable Brand Visibility | Includes the check in every AEO audit for this scope |
| Run Mode | Background queue (recommended) or inline |
| Brand Name | Defaults to the store name |
| Brand Domain | Defaults to the store base URL host |
| Brand Aliases | Alternate spellings, comma separated |
| Store Category | What you sell, in two or three words |
| Top Products | One per line; derived from the catalogue when empty |
| Cache Lifetime | Hours; 0 disables caching completely |
| Log Prompts and Answers | Writes to var/log/angeo_aeo_brand_visibility.log |
Set the store category. When it is empty and the catalogue gives nothing
useful, prompts fall back to a generic phrase, and generic phrases are won by
the large marketplaces every time. This single field moves the score more than
any other.
Providers
Each provider has its own group with the same fields: enable, API key (stored
encrypted), model, grounding, max tokens, temperature and timeout.
| Provider | Grounded answers | Notes |
|---|---|---|
| ChatGPT | Optional | Needs a search-capable model such as gpt-4o-search-preview |
| Claude | Optional | Uses the server-side web search tool; billed per search |
| Perplexity | Always | The most responsive signal of the five |
| Gemini | Optional | Uses Google Search as a tool |
| Groq | No | Free tier; a memory-only baseline |
Model identifiers change often. Pick the closest option, and expect to revisit
the field after a provider release.
Queries
Six built-in prompts, each with its own on/off switch and optional override
text: best stores in category, where to buy, tell me about this brand, product
search, compare with competitors, gift guide. Add your own under Custom
Prompts, one per line, as key: prompt text.
Placeholders: {{brand}}, {{domain}}, {{category}}, {{products}}.
Samples per Query controls how many times each pair is asked. Cost scales
linearly with it; the confidence interval narrows with the square root of it.
Analysis
Phrase matching supports English, Ukrainian, Dutch, German, French and
Spanish. English phrases are always matched in addition to the selected
language, because AI answers mix languages freely.
Extra Top-Level Domains matters for the accuracy check: without nl, de
or co.uk in that field, a wrong country domain attributed to your brand will
not be flagged.
Alerts
An alert fires when the score drops by more than the configured number of points
against the trailing average and the drop exceeds the confidence margin of
the run, or when a competitor outranks you, or negative tone or a wrong URL is
detected.
The webhook must be HTTPS, and hosts resolving to private or reserved addresses
are rejected unless an operator explicitly allows them.
Command line
# Full audit for the default scope
bin/magento angeo:aeo:brand-visibility
# One store view, ignoring the cache, with the action plan
bin/magento angeo:aeo:brand-visibility --store=2 --refresh --plan
# Machine-readable output
bin/magento angeo:aeo:brand-visibility --format=json
bin/magento angeo:aeo:brand-visibility --format=markdown
# One provider and prompt, printing the raw answer
bin/magento angeo:aeo:brand-visibility --provider=perplexity --prompt=brand_direct
# Fail a CI pipeline below a threshold
bin/magento angeo:aeo:brand-visibility --fail-on=50
| Option | Meaning |
|---|---|
--store |
Store view id (default 0) |
--refresh, -r |
Bypass the result cache |
--provider |
Query one provider instead of the full audit |
--prompt |
Prompt key used with --provider |
--format |
table, json or markdown |
--plan |
Print the action plan |
--fail-on |
Exit code 1 below this score |
Admin panel
Marketing → AEO Brand Visibility
- Dashboard — pick a store view, start a run, watch it complete, read each
answer, and generate the action plan from the latest stored run. - Audit History — a standard grid with filters, plus a detail page and CSV
export per run.
Access is split into four permissions, so an agency can be given read access
without the ability to spend money on API calls:
| Resource | Grants |
|---|---|
Angeo_AeoBrandVisibility::view |
Read reports and history |
Angeo_AeoBrandVisibility::run |
Start audits and single test queries |
Angeo_AeoBrandVisibility::export |
Download CSV |
Angeo_AeoBrandVisibility::config |
Change the configuration |
Scheduling and retention
Scheduled runs use the dedicated angeo_brand_visibility cron group, so slow
provider calls cannot delay Magento's default group. A second job applies the
retention policy nightly: runs older than the configured age, and runs beyond
the per-store-view budget, are deleted.
Cost
Cost is providers × prompts × samples requests per run. With three providers,
three prompts and three samples that is 27 requests. Weekly runs on small models
are cents; grounded searches on large models are not. Start with samples = 3
and one grounded provider.
Development
vendor/bin/phpcs --standard=vendor/angeo/module-aeo-brand-visibility/phpcs.xml
vendor/bin/phpstan analyse -c vendor/angeo/module-aeo-brand-visibility/phpstan.neon
vendor/bin/phpunit -c vendor/angeo/module-aeo-brand-visibility/phpunit.xml
Adding a provider takes an AiProviderInterface implementation and one line in
the ProviderPool argument of etc/di.xml. No existing class needs editing.
Related modules
angeo/module-aeo-audit— the audit framework this checker plugs intoangeo/module-llms-txt— publishllms.txtfor AI crawlersangeo/module-rich-data— stronger Organization and Product schemaangeo/module-robots-txt-aeo— control which AI crawlers may read the store
License
MIT. See LICENSE.
Built by Ievgenii Gryshkun — Magento 2 solution architect,
Netherlands.
Changelog
All notable changes to angeo/module-aeo-brand-visibility are documented here.
The format follows Keep a Changelog and
the project uses Semantic Versioning.
[4.0.0] — 2026-08-31
A rewrite for the Adobe Commerce Marketplace technical requirements, plus fixes
for the data-loss and methodology problems found in 3.0.0.
Breaking
BrandVisibilityServiceInterface::run()takes a fourth argument,
?int $auditResultId, so a queued run can be written back onto its own row.AuditResultRepositoryInterface::saveReport()takes?int $id;
createPendingRun()andmarkFailed()were added;pruneOldRecords()was
replaced byprune(int $maxPerStore, int $maxAgeDays).BrandQueryResultnow represents a cell aggregated over N samples. Its
signalsarray of booleans becamesignalRates(percentages), and
rawResponsebecameresponseswith agetRawResponse()accessor.- Configuration path
angeo_brand_vis/queries/queries_per_providerwas renamed
to.../max_prompts;0now means "no cap" rather than "cap at zero". angeo_brand_vis/analysis/llm_judgewas removed. It cost a second API call
per answer and produced a number that could not be compared between runs.- Admin route
angeo_brand_vis/query/singlebecameangeo_brand_vis/query/test
and is POST-only;angeo_brand_vis/history/viewdatawas removed in favour of
the UI component grid. - The single ACL resource was split into
::view,::run,::exportand
::config. Existing admin roles must be re-granted. - New database columns are added by declarative schema. Runs stored by 3.0.0
keep their scores, but have no samples, margin or competitive data.
Added
- Repeated sampling with a 95% confidence interval on every score. The reported
±margin makes it possible to tell a real regression from model variance. - Web-grounded answers per provider: Claude's server-side search tool, Gemini's
Google Search tool, OpenAI search-preview models. Perplexity is always
grounded; Groq cannot be. - Three-valued tone. Negative wording around the brand is detected and reduces
the earned score by a configurable penalty. - Real share of voice, weighted by mention count and by how early the brand
appears, replacing the1/Nplaceholder. - Asynchronous runs over the message queue, with a pending row, a polling admin
screen and an inline fallback for installations without consumers. - A dedicated
angeo_brand_visibilitycron group, and a nightly retention job
with a per-store-view row budget and a maximum age. - A standard UI component grid for audit history, with filters, bookmarks and
row actions. - Configurable extra top-level domains, so the accuracy check recognises
nl,
de,co.ukand other country domains. - Unit tests for the aggregator, the share-of-voice calculator, the report
aggregation, the webhook guard and the configuration reader. i18n/en_US.csv,etc/csp_whitelist.xml,phpcs.xml,phpstan.neonand
phpunit.xml.
Changed
- Providers are injected as an array through
ProviderPoolindi.xmlinstead
of being hard-coded in the service constructor. A new provider is now one
interface implementation and one di.xml line. - All HTTP traffic uses the framework HTTP client instead of raw cURL calls, and
all JSON goes throughSerializerInterface. - The admin templates contain no inline
<script>, no inline<style>and no
event attributes. Behaviour moved to a RequireJS module loaded through
data-mage-init, so the screens work under a restrictive content security
policy. - The action plan is built from the latest stored run instead of triggering a
fresh billable audit on every request. - Retention runs in its own cron job rather than inside every save.
- OpenAI reasoning models receive
max_completion_tokensand notemperature,
so o-series and search-preview models no longer return HTTP 400. - Provider labels, model option lists and the analysis language list are derived
from code rather than duplicated in static arrays.
Fixed
- Analyser metadata — competitors, share of voice, winner, tone and accuracy —
is now persisted. In 3.0.0saveReport()dropped themetakey, so the
headline feature of that release was lost on write and never reached the
history or the CSV export. getLatest(),getStatistics()and retention are scoped by store view.
Previously a multi-store installation averaged unrelated brands together, and
alerts compared a store against that mixed average.- The audit checker honours the
StoreInterfaceit is given. Previously the
argument was ignored and every store view was scored against the default
scope. cache_ttl_hours = 0disables the cache, as the field comment always claimed;
it was previously coerced back to 24. The same fix applies to a temperature of
0.- A prompt override text is ignored while its enable toggle is off.
- The result cache lifetime is read from the scoped configuration rather than
the unscoped instance. - The Gemini API key travels in the
x-goog-api-keyheader instead of the query
string, where it was written to proxy and access logs. - The alert webhook is validated: HTTPS only, and hosts resolving to private or
reserved addresses are rejected unless explicitly allowed. This closes a
server-side request forgery path to internal services. - The CLI used an invalid
<e>style tag, which threw instead of printing the
error it was trying to report. - Dutch and German separable verbs ("raad ik … aan", "empfehle ich") are matched
as recommendations. magento/module-emailis declared incomposer.jsonand sequenced in
module.xml; it was used but never required.- Documentation drift: the supported analysis languages are
en, uk, nl, de, fr, es(there is no Russian phrase set), negative sentiment is real rather than
aspirational, and the CLI description lists every provider.
Security
- No raw cURL, no API keys in URLs, no unvalidated outbound requests.
- Every billable action is POST-only and behind its own ACL resource.
- Admin output is escaped in PHP and built with the DOM API in JavaScript, so a
provider answer cannot inject markup into the admin panel.
[3.0.0]
Added
- Competitor tracking, share of voice and answer-accuracy checking.
- Gemini and Groq providers.
- Recommendation engine and CSV export.
[1.1.1]
Added
- Initial release: ChatGPT, Claude and Perplexity providers, five scoring
signals, admin dashboard and thelive_signalaudit checker.
| Version | Stability | QA Status | Compatibility | Released |
|---|---|---|---|---|
| 4.0.1 | stable | Fail | Magento 2.4.7-2.4.9 Details | 2026-09-17 19:30:00 |
| 4.0.0 | stable | Fail | Not compatible Details | 2026-09-06 16:44:57 |
| 3.0.0 | stable | Not tested | Not yet tested Details | 2026-09-06 16:43:51 |
| 2.0.0 | stable | Not tested | Not yet tested Details | 2026-09-06 16:42:55 |
| 1.2.0 | stable | Fail | Magento 2.4.7-2.4.8 Details | 2026-06-14 18:51:44 |
| 1.1.1 | stable | Fail | Magento 2.4.7-2.4.8 Details | 2026-05-28 19:14:06 |
| 1.1.0 | stable | Not tested | Not yet tested Details | 2026-05-28 19:09:11 |
| 1.0.0 | stable | Not tested | Not yet tested Details | 2026-05-16 19:15:52 |
Requires 11
| Package | Constraint |
|---|---|
| angeo/module-aeo-audit | ^3.0||^4.0 |
| magento/framework | ^103.0 |
| magento/framework-message-queue | ^100.4 |
| magento/module-backend | ^102.0 |
| magento/module-catalog | ^104.0 |
| magento/module-config | ^101.2 |
| magento/module-cron | ^100.4 |
| magento/module-email | ^101.1 |
| magento/module-store | ^101.1 |
| magento/module-ui | ^101.2 |
| php | ~8.1.0||~8.2.0||~8.3.0||~8.4.0||~8.5.0 |
Requires-dev 4
| Package | Constraint |
|---|---|
| bitexpert/phpstan-magento | ^0.43 |
| magento/magento-coding-standard | ^40.0 || ^41.0 |
| phpstan/phpstan | ^2.0 |
| phpunit/phpunit | ^10.5 |
Suggests 3
| Package | Reason |
|---|---|
| angeo/module-llms-txt | Publish llms.txt so AI crawlers can discover your catalogue structure. |
| angeo/module-rich-data | Strengthen Organization and Product schema for entity recognition. |
| angeo/module-robots-txt-aeo | Control which AI crawlers may read your store. |
Compatibility
Each Magento release line is installed on its supported PHP versions, then the module is built (DI compilation + static-content deploy) and its unit and integration suites are run. The matrix shows the lines and PHP versions the module is confirmed to install and run on. Code-quality results further down (phpstan, phpcs, …) are reported separately and never affect compatibility.
Code Quality
Advisory checks against the module's source. Static analysis runs once across the whole module; PHPStan re-runs per Magento + PHP version because resolvable symbols differ between releases. These NEVER affect the Compatibility badge. A phpcs finding can't make a module incompatible.
Static analysis
Coding standards (phpcs), mess detection (phpmd), copy-pasted code (cpd), PHP cross-version compatibility, composer.json validity. Each runs once for the whole module.
Tests
Unit and integration suites, run for each applicable Magento and PHP version. A test failure speaks to the module's behaviour, not its compatibility with a Magento line, so it is reported here separately and never reddens the compatibility matrix.
Unit tests
| Magento | PHP 8.2 | PHP 8.3 | PHP 8.4 | PHP 8.5 |
|---|---|---|---|---|
| 2.4.7 | N/A | N/A | ||
| 2.4.8 | N/A | N/A | ||
| 2.4.9 | N/A | N/A |
Integration tests
| Magento | PHP 8.2 | PHP 8.3 | PHP 8.4 | PHP 8.5 |
|---|---|---|---|---|
| 2.4.7 | N/A | N/A | ||
| 2.4.8 | N/A | N/A | ||
| 2.4.9 | N/A | N/A |
Security
Security checks run directly against the module: an audit of its declared dependencies for known vulnerabilities (composer audit) and a scan of its source for malware and web-shell signatures. Each runs once. A malware detection fails the version outright.
More from angeo
View vendorMagento 2 module for AI Engine Optimization (AEO). Generates spec-compliant llms.txt and llms-full.txt per llmstxt.org standard, plus streaming JSONL for vector indexing. Multi-store, multi-website, CLI, cron, async admin UI, Page Builder-aware sanitization, customer-group pricing, atomic writes, ETag/Cache-Control, .md mirrors.
Magento 2 module for AI Engine Optimization (AEO). Injects AI crawler rules (OAI-SearchBot, GPTBot, ChatGPT-User, PerplexityBot, Perplexity-User, Google-Extended, ClaudeBot, anthropic-ai, Claude-User, Applebot, cohere-ai, Amazonbot, Meta-ExternalAgent) into robots.txt — without overwriting your existing configuration. Supports per-bot Allow/Disallow lists, Crawl-delay, Sitemap directives, multi-store, and a public Api\RobotsStatusInterface for cross-module integration with angeo/module-aeo-audit.
Magento 2 AEO (AI Engine Optimization) Audit. v3 covers 15 signals — robots.txt AI bots, llms.txt + llms.jsonl, Product / Organization / FAQ schema, merchant return + shipping policies, sitemap.xml, UCP profile, AI product feed, OG tags, canonical + hreflang, JSON-LD quality, well-known endpoint matrix, Core Web Vitals via CrUX. Score Trend dashboard, Admin UI, cron, dynamic fix commands, dependency-injected extension point for custom checkers.
Spec-compliant Universal Commerce Protocol (UCP) profile generator for Magento 2. Generates /.well-known/ucp at protocol version 2026-04-08 with ECDSA P-256 signing keys, declared capabilities, and proper cache headers. v0.1.x is profile-only — catalog, cart, checkout endpoints land in later releases.
Turn an existing module into recurring revenue.
If you already maintain a Magento 2 module on GitHub or GitLab, listing it on Packagento takes about five minutes. We mirror your tags, handle distribution signing, and route paid licenses through Stripe Connect, so you can keep shipping the way you already do.