Orange Collar Media
Orange Collar Media
DOCS
OCMLABS/MODULE-SEO · ARCHITECTURE

How SEO Suite is built - one analyzer, head output, the SEO table, and extension points

  • Magento
  • Adobe Commerce
  • Hyvä

Module OCMLabs_Seo, namespace OCMLabs\Seo, Composer package ocmlabs/module-seo, plus the ocmlabs/module-seo-commerce Adobe Commerce licensing metapackage and the ocmlabs/module-seo-hyva Hyva companion (Hyva_OCMLabsSeo). Declarative schema only.

Design rules

  • Magento’s fields stay Magento’s. Meta titles, descriptions, and keywords stay in core meta_title, meta_description, and meta_keyword. The module stores only what core has no field for.
  • Never break a save or a page. Save-time scoring runs after the save commits, and any problem is logged and ignored. Storefront output falls back to Magento’s own behavior if a step fails, so a problem in this module can show up as a missing tag but never as an error page.
  • One analyzer. The live panel, save-time scoring, dashboard Re-score, the CLI, and cron all call the same analyzer, so every surface shows the same score.

The analyzer

Model\Analysis\Analyzer takes an analysis input (entity type, store, locale, focus and secondary keywords, meta title and description, URL key, name, content HTML, image alt texts) and runs every check that applies to the entity type. Content HTML, including Page Builder markup, is normalized to plain text, sentences, and paragraphs first, skipping script, style, noscript, and template elements.

Checks implement CheckInterface and are registered in the analyzer’s checks argument in etc/di.xml, keyed by ID (keyword_set, keyword_in_title, and so on). A check declares its weight and the entity types it applies to, and returns pass, warn, fail, or skip with a message and fix hint. Other modules can add or remove checks through that di.xml array.

  • Keyword checks extend AbstractKeywordCheck, which skips when no focus keyword is set.
  • readability_flesch and sentence_length extend AbstractReadabilityCheck, which skips unless the store locale starts with en_. paragraph_length does not, and runs on every locale.

Score = (passed weight + half the warned weight) / applicable weight x 100, with skipped and non-applicable checks left out of both sides.

Live analysis

The panel is a Knockout UI component on the product, category, and CMS page forms (the admin is the same on Luma and Hyva stores). It posts the unsaved form values to ocmlabs_seo/analysis/run, debounced, and renders the result. The controller requires OCMLabs_Seo::analyze and never writes anything.

Save, delete, and scoring

EventBehavior
catalog_product_save_commit_after, catalog_category_save_commit_after, cms_page_save_commit_afterWrites the submitted SEO values for the scope and scores the entity
catalog_product_delete_commit_after, catalog_category_delete_commit_after, cms_page_delete_commit_afterRemoves the entity’s SEO rows

ocmlabs_seo_rescore_changed (daily at 03:00, default group) re-scores rows changed since their last score and entities with no score. bin/magento ocmlabs:seo:rescore scores in batches of 500 through lazy proxies, so it does not slow bin/magento start-up. All errors go to var/log/ocmlabs_seo.log, a dedicated Monolog handler.

Head output

The module writes the head tags after core has set its own:

  • Category, CMS, home, and search pages are handled by an observer on layout_generate_blocks_after.
  • Product pages are handled by a plugin on Magento\Catalog\Helper\Product\View::prepareAndRender, because core rewrites the product’s meta title, meta description, and canonical after that event. On product pages the module also replaces core’s og:* block with a price-only version so Open Graph tags are not repeated; with the module off, core’s block is untouched.

With canonical management on, existing rel="canonical" links, including ones added by layout, are removed before the module adds its own.

Structured data

JSON-LD is assembled from a schema pool of builders (product, breadcrumb, organization, website) registered in di.xml on Model\Schema\SchemaPool, and rendered as one application/ld+json script. Values are JSON-encoded with script-closing sequences neutralized.

Caching

Everything the module adds to the storefront is part of the full-page-cached HTML, built from data already on the page. It adds no per-request database reads outside page generation and no cache tags of its own.

When a save or the Set Robots mass action changes a storefront-visible value (robots, canonical URL, social title, description, or image), the module cleans the entity’s existing core cache tag (cat_p_<id>, cat_c_<id>, or cms_p_<id>) through Magento’s standard clean_cache_by_tags event, so Varnish is purged as well. Core purges that tag when the entity is saved, but the module’s row is written afterwards, and the mass action never saves the entity, so the module cleans it again. Score-only updates never clean the cache. A failed clean is logged and the save carries on.

Database

TablePurpose
ocmlabs_seo_entityOne row per entity type, entity ID, and store ID (store 0 for the default scope): focus keyword, secondary keywords (JSON array, up to 4), score, the analysis check list, grid flags, robots and canonical overrides, og_title, og_description, og_image, scored_at, updated_at. Unique on (entity_type, entity_id, store_id).

Store-view rows override the default row field by field; an empty store-view field inherits. CMS pages have only the default row.

Configuration access

All settings are read through Model\Config, a typed reader over ScopeConfigInterface. The General switches and the x-default store view are default-scope settings; everything else can vary by website and store view.

Admin and ACL

The dashboard sits under Marketing > SEO & Search (Magento_Backend::marketing_seo). ACL resources sit under Magento_Backend::marketing in OCMLabs_Seo::main: ::dashboard (with child ::dashboard_manage for mass actions), ::analyze, and ::config. All admin and storefront strings are in i18n/en_US.csv.

Uninstall

bin/magento module:uninstall OCMLabs_Seo --remove-data drops ocmlabs_seo_entity, deletes the ocmlabs_seo/* configuration rows, and removes the nightly job’s timestamp. Products, categories, and CMS pages are untouched.