Skip to content

Architecture

OCMLabs_Faq follows standard Magento 2 patterns throughout. This page is the developer reference for what is inside the module and where each piece lives.

  • Package: ocmlabs/module-faq
  • Module name: OCMLabs_Faq
  • Current version: 1.0.5
  • Requires: PHP 8.3+, Magento 2.4.6 to 2.4.8 (framework >=103.0.6 <103.0.9, catalog, backend, ui modules)
  • Editions: Magento Open Source and Adobe Commerce (Adobe Commerce via the ocmlabs/module-faq-commerce metapackage)
  • Accessibility: WCAG 2.1 Level AA conformant storefront accordion

OCMLabs_Faq adds a product FAQ system to Magento 2. Admins create FAQ entries - a question, an answer, and one or more associated products - and those FAQs appear in an accordion tab on each associated product detail page. Each FAQ also emits a JSON-LD FAQPage schema block, which makes the questions eligible for Google’s rich result FAQ display in search.

A single FAQ can be assigned to multiple products. A product can have multiple FAQs, displayed in a configurable sort order.

Two tables manage FAQ data:

ocmlabs_faq - stores FAQ records: question, answer, sort order, active flag, and timestamps.

ocmlabs_faq_product - a junction table linking FAQs to products (faq_id, product_id). Enforces a unique constraint on the pair and carries cascade deletes on both sides. This is what enables the many-to-many relationship.

The module follows Magento’s standard repository pattern:

  • Api/Data/FaqInterface defines the data contract: entity ID, product IDs array, question, answer, sort order, is_active, and timestamps.
  • Model/Faq implements the interface via AbstractModel.
  • Model/FaqRepository implements FaqRepositoryInterface with standard getById, save, delete, and getList (SearchCriteria) methods.
  • Model/ResourceModel/Faq handles the product relationship in _afterLoad (reads product IDs from the junction table) and _afterSave (delete-then-insert pattern to sync product links on every save).
  • Model/ResourceModel/Grid/Collection powers the admin grid with a LEFT JOIN + GROUP_CONCAT producing a computed product_skus column. Supports cross-table SKU filtering.

The admin interface lives under Catalog > Product FAQs (sortOrder 50) and provides:

  • Grid listing (ocmlabs_faq_listing) - all FAQ records with columns for question, product SKUs, sort order, and status. Supports search and mass delete.
  • Create / Edit form (ocmlabs_faq_form) - fields for question, answer, sort order, active toggle, and an Associated Products section with a searchable product grid.
  • System configuration (etc/adminhtml/system.xml) - store-view-scoped settings for the FAQ heading title, tab title, heading visibility, and tab visibility under Stores > Configuration > OCMLabs > Product FAQs.
  • Save controller - reads product IDs from the faq_products JSON payload submitted by the product grid and persists them to the junction table.
  • DataProvider (FaqFormDataProvider) - loads FAQ fields for the edit form. Product associations are pre-populated in the product grid via Block/Adminhtml/Faq/AssignProducts.
  • AssignProducts block (Block/Adminhtml/Faq/AssignProducts.php) + Block/Adminhtml/Faq/Tab/Product.php - renders the searchable product grid in the form, pre-selecting currently assigned products.
  • ACL - single permission resource OCMLabs_Faq::manage under Magento_Backend::content. Controls access to the entire admin section.
ControllerRoutePurpose
Indexocmlabs_faq/faq/indexGrid listing
Editocmlabs_faq/faq/editEdit form
NewActionocmlabs_faq/faq/newNew FAQ form
Saveocmlabs_faq/faq/savePOST save handler
Deleteocmlabs_faq/faq/deleteSingle record delete
MassDeleteocmlabs_faq/faq/massDeleteBulk delete from grid

Block/Product/FaqTab loads active FAQs for the current product, sorted by sort_order ascending. The block renders nothing if no active FAQs exist for the product, or if the FAQ tab is disabled in store configuration, so no empty tab or section appears.

The FAQ tab is injected into the product detail page via catalog_product_view.xml at sort_order="20", placing it early in the tab list. Its visible tab label is resolved from store configuration at render time.

Renders a <section> with an accessible accordion following the WAI-ARIA APG Accordion Pattern. The section heading is store-configurable and can be hidden independently from the tab label. Each FAQ item is a native <button> wrapped in <h3> (so headings-list navigation jumps between FAQs), carrying aria-expanded and aria-controls. The corresponding answer panel has role="region" and aria-labelledby pointing back at the question button id, giving assistive tech a clear question-to-region relationship. The first item is expanded by default. A chevron SVG icon rotates on toggle. Behaviour is driven by a RequireJS component (faqAccordion) following Magento’s standard data-mage-init pattern. All DOM ids in the rendered markup are scoped to the block’s layout name, so multiple accordion instances on a single page cannot collide.

See Accessibility for the WCAG 2.1 Level AA audit details.

Injects a <script type="application/ld+json"> block with @type: FAQPage structured data. Questions and answers are strip-tagged before output. The block is skipped entirely if no FAQ data is present, or if the FAQ tab is disabled for the current store view.

See SEO (JSON-LD) for the on-page behaviour and Google eligibility rules.

  • No REST or GraphQL API. No webapi.xml is included. FAQ data is not exposed via Magento’s API layer.
  • Store-scoped display configuration. system.xml provides store-view overrides for FAQ heading text, tab text, heading visibility, and tab visibility. Clearing the tab title falls back to the layout-default “FAQs” label so the product tab control always has an accessible name.
  • No plugins or observers. The module adds no extension points and hooks into no external events.
  • Admin-only CRUD. FAQ management is entirely through the Magento admin panel.
  • WYSIWYG answers. The answer field uses a WYSIWYG editor with a restricted toolbar in the admin. Output is rendered as raw HTML - trusted as admin-authored content gated behind OCMLabs_Faq::manage. The schema template strips all tags before including answers in JSON-LD output.

The module is distributed as two Composer packages from a single repository:

PackageTypePurpose
ocmlabs/module-faqmagento2-moduleThe module itself. Installs on Magento Open Source and Adobe Commerce.
ocmlabs/module-faq-commercemetapackageAdobe Commerce licensing gate. Requires ocmlabs/module-faq plus magento/product-enterprise-edition.

The community package gates Magento versions via magento/framework (present in all install methods including GitHub clones) and holds conflict rules against both edition metapackages at >=2.4.9. Both packages are version-locked - ocmlabs/module-faq-commerce requires ocmlabs/module-faq: ^1.0 and ships the matching tag.