Sylius SEO Suite Pro: Troubleshooting
Troubleshooting for Sylius SEO Suite Pro: robots.txt, SEO fields, setup, hreflang, JSON-LD, OG images, audit, redirects and the 404 log with symptom, check, solution, plus known limitations and FAQ.
Sylius SEO Suite Pro: Troubleshooting
This page lists typical problems in the pattern Symptom, Check, Solution. Every statement comes from the behaviour of the plugin code. The basics are in the installation and configuration pages.
Almost every plugin feature falls back silently to the default behaviour when a table is missing or something fails, and writes a warning to the log. For any problem, look in your environment's log first and search for
mmd/sylius-seo-suite-pro-plugin.
/robots.txt returns 404 or not the expected content
Symptom: /robots.txt answers with 404, with a file from the web root, or with User-agent: * and Allow: /, although you maintained custom content in the admin.
Check:
- The shop routes must be imported at the top level, not under the
/{_locale}prefix. Otherwise the route only exists as/{_locale}/robots.txt. - The routes must exist in the router:
bin/console debug:router mmd_sylius_seo_suite_pro_robots_txtmust show the path/robots.txt. - Is there a physical file
public/robots.txtin the web root? The web server serves it before Symfony sees the request. - The channel of the requested domain needs an entry in the channel settings with robots.txt content. Without an entry, or when the domain cannot be resolved to a channel, the plugin deliberately answers with the permissive default
User-agent: *andAllow: /. - The response is publicly cacheable (
max-age3600 seconds) as long as no session was started. A cache in front can serve old content.
Solution: Import @MmdSyliusSeoSuiteProPlugin/config/routes/shop.yaml at the top level (see installation), remove a static public/robots.txt, maintain the content in the channel settings (see configuration) and purge the cache in front if needed.
SEO fields are missing in the product or category form
Symptom: The admin has no fields for meta title, canonical override and robots on products and categories. Categories also lack the meta description.
Check:
- The fields are an opt-in. They only appear when the shop's translation class implements
SeoAwareTranslationInterface(withSeoAwareTranslationTrait). - For the meta description of a category, the taxon translation must additionally implement
MetaDescriptionAwareTranslationInterfacewith the matching trait. For products the plugin uses the core meta description field. - The extra columns need a migration in your shop. The plugin does not change core classes.
Solution: Follow the opt-in guide in the installation, create the migration for your translation tables and clear the cache afterwards (bin/console cache:clear).
The setup command aborts
Symptom: bin/console mmd:seo-suite-pro:setup ends with an error, or the plugin tables do not exist afterwards.
Check:
- Message
Migrations this plugin builds on are still open. Run "bin/console doctrine:migrations:migrate" first...: migrations of Sylius itself are still open. The command then executes nothing. - Message
The migrations of this plugin are not registered with Doctrine...: the bundle is not enabled orsylius_core.prepend_doctrine_migrationsis set tofalse. - Notice
The routes of this plugin are not imported yet: this is only a warning with a YAML snippet to copy. The command does not abort for it and never writes into yourconfig/. - If the database is not MySQL or MariaDB, all plugin migrations skip themselves and are recorded as executed. The tables then do not exist.
Solution: Run bin/console doctrine:migrations:migrate first and then the setup command again. Enable the bundle in config/bundles.php. The plugin does not work on database platforms other than MySQL or MariaDB (see Known limitations). If an admin screen shows an error after an update, a table is usually missing: run the setup command after every plugin update.
The admin sidebar entry is missing
Symptom: The admin navigation shows no MMKreativ Plugins group, or the group without the SEO Suite Pro entry.
Check:
- Are the admin routes imported (
@MmdSyliusSeoSuiteProPlugin/config/routes/admin.yaml)? If they are missing, the plugin silently omits the menu entry so that not every admin page fails. - Does the admin account have access, and was the cache cleared after installation?
Solution: Import the admin routes, clear the cache and reload the admin. All other screens (templates, redirects, hreflang, schema, audit, OG images) are reached through the buttons on the channel settings, not through separate menu items.
No hreflang links are rendered
Symptom: The head of a product, category or home page lacks the <link rel="alternate" hreflang> tags.
Check:
- Only product, category and home pages are covered. Custom routes need their own provider (see extending).
- Minimum count: without at least
hreflang.min_alternatesalternates (default 2,x-defaultnot counted) with distinct URLs, nothing is rendered. A channel with a single language therefore produces no tags. - Self-reference: the current page itself must be part of the set, otherwise the whole output is dropped. A disabled product, a product not in this channel or a missing translation can cause that.
- The channel's hreflang settings: if
enabledis off, there is no output. - Groups: all channels of a group (
group_code) need a hostname. A channel without a hostname is left out of the other channels' alternates (warning in the log). Disabled channels and settings withenabledoff are skipped as well. - Duplicate values: if two channels of one group claim the same hreflang value, the setting with the lower ID wins and the other is left out with a warning.
- Filtered or sorted listings, pages beyond page 1 and
noindexpages deliberately get no hreflang links.
Solution: Maintain hostnames and languages of the channels, adjust hreflang.min_alternates and the format, or give each channel different languages. Details are on the hreflang page.
GTIN, MPN or brand are missing in the JSON-LD
Symptom: The Product node contains no gtin8/gtin12/gtin13/gtin14, no mpn or no brand.
Check:
- The attribute for GTIN and MPN must be of type text. An integer attribute is rejected by validation because 32-bit numbers would corrupt long codes. The brand may be text or select.
- The attribute code in the channel's schema settings, or in
brand_attribute_code(defaultbrand), must match the product attribute code exactly, and the product needs a value for the current language or a language-independent value. - A GTIN is only rendered with 8, 12, 13 or 14 digits and a valid check digit. Otherwise it is dropped and the plugin logs a warning with the product code. Spaces and hyphens are removed first.
- An MPN longer than 70 characters is not rendered.
- In the
selectedmode of the additional properties at least one attribute code must be chosen. At mostschema.max_additional_properties(default 25) properties are rendered.
Solution: Create the attribute as text, fix code and value and check the result in the Shop API under structuredData. More under structured data.
OG images: 503, placeholder or normal product image
Symptom: The card URL under /seo/og/... answers with 503 or 404, redirects to the normal product image, or the card appears without the product image.
Check:
- Cards are off per channel by default. In the OG image settings the cards and the type (products, categories) must be enabled. For a disabled channel or type the route answers 404, as it does for invalid parameters.
- GD with FreeType: if
ext-gdor FreeType is missing, no card can be drawn. The visitor is redirected (302) to the normal image and the cause is logged as a warning. - Format:
og_image.format: webpforces WebP. If GD has no WebP support, that is a hard error at render time. Withautothe plugin picks WebP or JPEG itself. - Font files:
og_image.font_regularandog_image.font_boldmust be readable (the defaults are the shipped DejaVu fonts). - Remote image sources: if
og_image.remote_hostsis set, only these hosts are fetched. Loopback, private and reserved addresses are always refused, including local CDN setups in a Docker network. The card is then drawn without the image. - Time budget: if image and logo together exceed
og_image.render_budget(default 15 seconds), a degraded card is produced. It is cached for only 300 seconds and not stored, the next request draws it again. - 503 with
Retry-After: 5means another process is drawing the same card right now. This resolves itself. - An image replaced under an unchanged path or URL is not noticed.
Solution: Install GD with FreeType (and WebP, or set format to auto or jpeg), allow the image host in remote_hosts or serve the images from a public address, and pre-render cards after imports or image changes with bin/console mmd:seo-suite-pro:og-image:generate. mmd:seo-suite-pro:og-image:prune removes old or orphaned files. See OG images.
The SEO audit is stuck or truncated
Symptom: An audit reports An audit of the channel "…" is already running., stays on Running in the dashboard, or ends with a note about the limit.
Check:
- At most one audit runs per channel (lock via the unique
lock_key). An aborted run only blocks until its heartbeat is older thanaudit.stale_after_minutes(default 10). After that the next run takes it over and closes it. - Message
Stopped at the subject limit (audit.max_subjects): the channel has more products or categories thanaudit.max_subjects(default 20000). The results then cover only part of the catalogue and the command exits with a failure code. - The Run audit now button in the admin runs synchronously with a 600 second limit. Large catalogues belong on the console.
- Message
The audit tables are missing: run doctrine:migrations:migrate.: the migration is missing.
Solution: Wait for the stale time or shorten it, raise audit.max_subjects and run the audit as a cronjob with bin/console mmd:seo-suite-pro:audit:run. For missing tables run mmd:seo-suite-pro:setup. See audit.
Redirects do not fire
Symptom: A redirect exists, but the page still returns 404 or the wrong target.
Check:
- Redirects only apply when Symfony raises a 404 (GET and HEAD only). A URL that still has a page (for example a product that is still active) is not redirected.
- Matching uses the path without query string and without host. Case and a trailing slash do not matter. Mind the shop's locale prefix: the source must contain the real path including the
/{_locale}part. - Channel: a redirect with a channel applies only there, one without a channel applies to all. A disabled redirect is ignored. If the redirect sits in the wrong channel, the 404 stays.
- Excluded are the admin path,
/apiand every path starting with/_. - Chains and loops are checked on save:
This would create a redirect loop...prevents loops, chains are flattened to one hop. The target must not equal the source. - If the redirect table is missing, the 404 stays a normal 404 and a warning is logged.
- On import every row is validated like the admin form and saved individually. Invalid rows are skipped.
Solution: Create the source as the real path in the right channel, check activation and the messages shown on save, and run mmd:seo-suite-pro:setup if tables are missing. See redirects.
The 404 log keeps growing
Symptom: The list of 404 paths gets very long or contains outdated entries.
Check:
- The log is capped: at
redirects.not_found_log_limit(default 5000) a new path replaces the one seen longest ago. Static files such as images are not counted. - Old rows are removed only by the prune command. It does not run by itself.
Solution: Schedule bin/console mmd:seo-suite-pro:redirects:prune-log (default 90 days, adjustable with --days) as a cronjob, lower the limit if needed, or switch counting off with redirects.log_not_found: false. See redirects.
Canonical or noindex on filtered listings is surprising
Symptom: Filtered or sorted category pages are noindex, point their canonical to the base page, or a custom filter is not recognised.
Check:
- A listing URL counts as filtered when at least one parameter from
facet_parameters(defaultcriteria,sorting,limit) has a non-empty value.pageis not part of it. - Filtered listings receive the robots setting of the channel settings (
facet_robots_index, default off, andfacet_robots_follow, default on), sonoindex,followby default. The canonical points to the base page, only the page number is kept. - Filters that live on their own URL path instead of the query string are not detected.
Solution: Add your parameters to facet_parameters (include the defaults you want to keep) and set index and follow in the channel settings as needed. See configuration.
The page title stays unchanged
Symptom: The <title> still shows Sylius' default title.
Check:
- The home page deliberately keeps the Sylius title (the channel name). Only product and category pages get the meta title.
- The title is replaced in the finished HTML. That works only if the response is
text/htmland the layout contains exactly one<title>element. - A manually maintained meta title of a translation takes precedence over templates. With neither, the name of the product or category is used.
Solution: Check the layout and the response type, and create an SEO template if needed.
Known limitations
- Sylius core has no CMS pages, so there is nothing to attach a meta title to beyond products and categories.
- Category pages get a
<meta name="description">only if the field of the taxon translation is filled or a description template applies. There is no automatic fallback to the category description. Use{taxon.description}in a template for that. - The SEO templates admin screen needs its table. Without the migration it answers with an error, while storefront and API fall back silently to the state without templates.
- The language field of the template form shows the languages of all channels. The check against the chosen channel happens on save.
- SEO templates: no preview in the form, no per-product template choice, no conditional parts (a template with an empty placeholder is skipped as a whole). The help text lists only the built-in placeholders. The uniqueness of the all-languages row is checked by validation, not by a database index, because MySQL does not treat two NULLs as equal in a unique index.
- A faceted listing is recognised by its query string. Filters that live on their own URL path are not detected.
- The title replacement assumes the layout renders exactly one
<title>element. - The redirect screens (list, form, import, 404 log) need their tables. Without the migration they answer with an error, while the storefront serves a plain 404. The 404 counting uses
INSERT ... ON DUPLICATE KEY UPDATEand works on MySQL or MariaDB only. - Redirects: chains are flattened when a redirect is saved, a later change to the end of a chain is not propagated. A redirect for all channels pointing at one for a single channel stays two hops (loops are still detected across channels). Only the path counts, never host or query string. Slug changes made by bulk SQL or DQL updates get no automatic redirect.
- hreflang: channels are only linked when you put them into a group, the plugin never matches products of different channels by name. Products and categories are matched by their code, so a group works when the channels share a catalogue. The home page is linked to the home pages of the group's channels. The settings screen needs its table, without it storefront and API use the defaults. A custom home or listing route other than
sylius_shop_homepage,sylius_shop_product_showorsylius_shop_product_indexneeds its own provider. An alternate is an absolute URL on the channel's hostname, and a reverse proxy that changes the scheme has to forward it. - Audit: it checks presence and length of the texts, images by count and the robots settings, nothing about alt texts or image sizes. Duplicate detection compares the delivered texts of one type and language inside a channel, not across channels. Languages without their own translation of a product or category are skipped. Without the audit tables the dashboard shows a hint, the command reports it and the findings list answers with an error. Storefront and API are not affected.
- OG images: one card per subject and channel in the channel's default language. An image replaced in place under the same path or URL needs the cards to be regenerated, because a page request may not read the image. No right-to-left or complex scripts and no CJK glyphs in the shipped font. Without the OG image settings table the screen answers with an error, while storefront, API and the card route behave as if no card existed.
- MySQL and MariaDB only: on other platforms the migrations are skipped and recorded as executed, so the tables do not exist.
FAQ
General
Does the plugin generate a sitemap? No. The plugin contains no sitemap generator. With SitemapExclusionInterface it only offers the exclusion logic (noindex, or a canonical pointing elsewhere) that a sitemap plugin can call. The optional mmd/sylius-sitemap-plugin is only suggested in composer.json.
How many installations does one licence cover? One Sylius installation with all its channels, plus its development and staging environments. Redistribution is not allowed, updates are included for 12 months from purchase. The LICENSE file is authoritative.
Is the plugin public? No, it is a private repository with a commercial licence.
Where do I find the settings? Under MMKreativ Plugins in the admin sidebar. All other screens are reached through the buttons on the channel settings.
Configuration
Where does configuration live? Global options sit under the key mmd_sylius_seo_suite_pro in config/packages/mmd_sylius_seo_suite_pro.yaml, all of them optional. Per-channel settings are maintained in the admin and stored in the database. Details in the configuration page.
Does the plugin read environment variables? No. All options come from the Symfony configuration and the database.
How is caching handled? The plugin keeps settings and templates in memory per request and resets them between requests. The robots.txt is publicly cacheable for one hour, card images for one year with immutable and an ETag derived from the content hash. After a plugin update, clear the Symfony cache with bin/console cache:clear, because the serializer stores its metadata there.
Which cronjobs do I need? mmd:seo-suite-pro:audit:run (nightly), mmd:seo-suite-pro:redirects:prune-log and mmd:seo-suite-pro:og-image:prune, and with OG images also mmd:seo-suite-pro:og-image:generate after imports. Run the setup command after installation and updates, not as a cronjob.
Compatibility
Which Sylius versions are supported? composer.json requires Sylius ^2.2, PHP ^8.3 and Symfony ^7.4. CI builds against Sylius 2.2 and 2.3.
Which API Platform versions? composer.json allows API Platform ^4.2.1 || ^5.0. CI covers Sylius 2.2 with API Platform 4.2 as well as Sylius 2.3 with API Platform 4.3 and 5.
Which databases? MySQL 8 and MariaDB only. CI runs against MySQL 8.4.
Does the plugin work with a headless frontend? Yes. Storefront and Shop API use the same SeoDataProviderInterface, so the two cannot drift apart. See Shop API.
Do I need GD for the plugin? Only for the optional OG images, with FreeType (WebP output additionally needs WebP support). Everything else works without GD.