magendoo / module-faq

magendoo/module-faq

Magento 2 FAQ and Product Questions module — SEO-optimized FAQ pages, product Q&A tabs, ask-a-question forms, ratings, and admin knowledge base management.

magento2-module Compatibility: 2.4.7-2.4.9 Code Quality: Fail Tests: N/A Security: Pass MIT

Are you the maintainer of magendoo?

Packagento pulls magendoo's Composer packages from the public registry so buyers can find them here.

Claim the namespace to take ownership, publish new releases directly, and start charging for premium versions.

Claim this namespace →

Magendoo FAQ & Product Questions Module for Magento 2

Magento 2
PHP
License

A FAQ and Product Questions management system for Magento 2: SEO-friendly FAQ pages, a
product-page Q&A tab with an ask-a-question form, moderation workflow, ratings, search with
analytics, CMS widgets, CSV import/export and a REST API.

Screenshots

Product Page — FAQ tab with questions and "Ask a Question" form

[image: Product Page FAQ Tab]

Admin — Question editor with WYSIWYG answer, status workflow, and SEO fields

[image: Admin Question Editor]

Admin — FAQ Questions grid with filtering, mass actions, and status management

[image: Admin Questions Grid]

Features

  • Hierarchical FAQ organization — categories, tags, and product associations
  • Search with analytics — storefront FAQ search plus an admin Search Terms report
  • Customer engagement — helpfulness voting (Yes/No or thumbs up/down) or a 1–5 star rating
  • Product page integration — a "Product Questions" tab with an ask-a-question form
  • REST API — 24 endpoints; anonymous storefront routes return a privacy-safe projection
  • Moderation workflow — Pending → Answered / Rejected, with optional email notifications
  • Compliance — GDPR consent recording (timestamp + wording snapshot), native reCAPTCHA support
  • SEO — clean URLs with URL rewrites, FAQPage JSON-LD, sitemap integration, canonical links,
    hreflang tags, per-entity robots meta
  • Multi-store — per-store-view assignment for categories and questions; customer-group
    restrictions for B2B/B2C segmentation

A merchant-oriented walkthrough lives in docs/user-guide.md.

Requirements

Requirement Version
Magento Open Source / Adobe Commerce 2.4.x (magento/framework >= 103.0.0)
PHP 8.1+

Developed and tested against Magento Open Source 2.4.9 on PHP 8.4. Depends on the
Magento_Sitemap, Magento_Widget and reCAPTCHA modules, all declared in composer.json
(they ship with every standard Magento installation).

Installation

Composer

composer require magendoo/module-faq
bin/magento module:enable Magendoo_Faq
bin/magento setup:upgrade
bin/magento cache:flush
bin/magento magendoo:faq:reindex

Manual

cp -r Magendoo/Faq app/code/Magendoo/
bin/magento module:enable Magendoo_Faq
bin/magento setup:upgrade
bin/magento cache:flush
bin/magento magendoo:faq:reindex

In production mode also run bin/magento setup:di:compile and
bin/magento setup:static-content:deploy.

Post-installation setup

  1. Configure the module: Stores → Configuration → Magendoo Extensions → FAQ
  2. Set admin permissions: System → Permissions → User Roles — the FAQ resources appear
    under Content → FAQ
  3. Enable reCAPTCHA (recommended): Stores → Configuration → Security → Google reCAPTCHA
    Storefront
    — set keys, then choose a type for Enable for FAQ Ask a Question Form
  4. Create content: Content → FAQ → FAQ Categories and Content → FAQ → FAQ Questions

Uninstallation

The module does not ship a Setup/Uninstall class, and Magento does not process a disabled
module's schema, so neither module:disable nor composer remove removes any data. To remove
the module completely:

bin/magento module:disable Magendoo_Faq
composer remove magendoo/module-faq   # or delete app/code/Magendoo/Faq
bin/magento setup:upgrade
bin/magento cache:flush

Then, only if you also want to delete all FAQ data, drop the tables and rows manually
(back up first):

DROP TABLE IF EXISTS magendoo_faq_question_tag, magendoo_faq_rating,
  magendoo_faq_question_customer_group, magendoo_faq_question_product,
  magendoo_faq_question_category, magendoo_faq_question_store,
  magendoo_faq_category_customer_group, magendoo_faq_category_store,
  magendoo_faq_search_log, magendoo_faq_question, magendoo_faq_category,
  magendoo_faq_tag;
DELETE FROM core_config_data WHERE path LIKE 'magendoo_faq/%';
DELETE FROM url_rewrite WHERE entity_type IN ('faq-category', 'faq-question');

Configuration Reference

All settings live under Stores → Configuration → Magendoo Extensions → FAQ and can be set per
store view. Defaults below come from etc/config.xml; fields without an entry there start
empty/off.

magendoo_faq/general

Field Default Description
enabled 1 Module on/off
title FAQ FAQ landing page title
url_prefix faq URL segment for all FAQ pages
allow_guest_questions 1 Allow guests to use the storefront ask form

magendoo_faq/navigation

Field Default Description
show_breadcrumbs 1 Breadcrumb display on FAQ pages
sort_categories_by position position, name or most_viewed
sort_questions_by position position, name or most_viewed
answer_length_limit 250 Character limit for answer previews in listings
show_search_box 1 Search box on FAQ pages
no_results_text (empty) Message when a search finds nothing
questions_per_category_page 10 Pagination on category pages
questions_per_search_page 10 Pagination on search results
short_answer_behavior short_answer Listing preview source: short_answer or cut_full_answer (truncated full answer)
tags_limit 20 Maximum tags in the tag cloud (0 = no limit)

magendoo_faq/product_page

Field Default Description
enabled 1 Show the questions tab on product pages
tab_name Product Questions Tab label; {count} is replaced with the question count
tab_position 40 Tab sort order
show_ask_button 1 Show the ask-a-question form in the tab
questions_limit 10 Maximum questions shown in the tab

magendoo_faq/rating

Field Default Description
enabled 1 Rating widget on question pages
type yes_no yes_no, voting (thumbs up/down) or average_rating (1–5 stars)
allow_guest_rating 1 Allow guests to vote; when off, voting requires login

magendoo_faq/social

Field Default Description
enabled 0 Social share buttons on question pages
networks (empty) Any of Facebook, Twitter, LinkedIn, Pinterest, Email

magendoo_faq/seo

Field Default Description
url_suffix_enabled 0 Append a suffix to FAQ URLs
url_suffix .html The suffix (when enabled)
remove_trailing_slash 0 Redirect trailing-slash URLs to the canonical form
use_canonical 0 Emit a <link rel="canonical"> on FAQ pages (per-entity override available)
structured_data_enabled 1 FAQPage JSON-LD on category and question pages
robots_search_results NOINDEX,FOLLOW Robots meta for the search results page
add_to_sitemap 1 Include FAQ pages in the XML sitemap
hreflang_enabled 0 Hreflang alternate links for multi-store setups
sitemap_frequency (empty — falls back to weekly) Sitemap change frequency
sitemap_priority (empty — falls back to 0.5) Sitemap priority

magendoo_faq/user_notifications

Field Default Description
enabled 0 Email the asker when their question is answered
email_sender general Sender identity
email_template magendoo_faq_user_notifications_email_template Template

magendoo_faq/admin_notifications

Field Default Description
enabled 0 Email the store owner when a new question is submitted
send_to [email protected] Comma-separated recipient addresses
email_template magendoo_faq_admin_notifications_email_template Template

magendoo_faq/gdpr

Field Default Description
enabled 0 Require a consent checkbox on the ask form
consent_text "I agree that my name and email address will be stored…" Checkbox label. When consent is given, the timestamp and the wording shown are stored on the question

reCAPTCHA

The ask form registers with Magento's native reCAPTCHA framework as
FAQ Ask a Question Form (recaptcha_frontend/type_for/magendoo_faq_question_submit) —
configure it under Stores → Configuration → Security → Google reCAPTCHA Storefront.


Storefront URLs

Implemented by a custom router (Magento\Framework\App\RouterInterface):

/faq/                                    → FAQ home (category listing + search)
/faq/{category-url-key}                  → Category page
/faq/{category-url-key}/{question-key}   → Question page (membership is verified;
                                           mismatched pairs redirect to the canonical URL)
/faq/{question-url-key}                  → Question page (via URL rewrite)
/faq/tag/{tag-url-key}                   → Tag page
/faq/search?q=keyword                    → Search results
/faq/question/suggest?q=keyword          → AJAX autocomplete (JSON)
  • Configurable URL prefix per store view; optional suffix.
  • Suffixed/unsuffixed and trailing-slash variants redirect to the canonical form
    (trailing-slash redirect gated by seo/remove_trailing_slash).
  • URL rewrites are generated on save, cleaned up on delete/unpublish, and validated for
    uniqueness (url_key is unique at the database level; collisions produce an actionable
    error naming the conflicting path).
  • bin/magento magendoo:faq:reindex purges and regenerates all FAQ rewrites.

CLI Commands

# Purge and regenerate FAQ URL rewrites for all categories and questions
bin/magento magendoo:faq:reindex

# Export questions or categories to CSV (default file: var/export/faq-{entity}.csv)
bin/magento magendoo:faq:export -e questions -f var/export/faq-questions.csv
bin/magento magendoo:faq:export -e categories

# Import questions or categories from CSV
bin/magento magendoo:faq:import -e questions -f var/export/faq-questions.csv
bin/magento magendoo:faq:import -e categories -f var/export/faq-categories.csv

Export includes the relation columns (store_ids, category_ids, product_ids, tags,
customer_group_ids) and defuses spreadsheet formula injection. Import updates rows whose
question_id/category_id matches an existing record, creates rows with an empty id column,
and skips (with a report) rows whose id does not exist.

REST API

24 routes in etc/webapi.xml. Auth column: admin = admin/integration token with the named
ACL resource; customer = a logged-in customer token; anonymous = no token required.

Endpoint Method Auth Description
/V1/faq/categories GET admin (Magendoo_Faq::category) List categories (SearchCriteria)
/V1/faq/categories/:id GET admin (Magendoo_Faq::category) Get category
/V1/faq/categories POST admin (Magendoo_Faq::category_edit) Create category
/V1/faq/categories/:id PUT admin (Magendoo_Faq::category_edit) Update category
/V1/faq/categories/:id DELETE admin (Magendoo_Faq::category_delete) Delete category
/V1/faq/questions GET admin (Magendoo_Faq::question) List questions (SearchCriteria)
/V1/faq/questions/:id GET admin (Magendoo_Faq::question) Get question (full object)
/V1/faq/questions POST admin (Magendoo_Faq::question_edit) Create question
/V1/faq/questions/:id PUT admin (Magendoo_Faq::question_edit) Update question
/V1/faq/questions/:id DELETE admin (Magendoo_Faq::question_delete) Delete question
/V1/faq/questions/submit POST customer Submit a question (see note below)
/V1/faq/questions/:id/rate POST anonymous Vote: positive/negative, or "1""5" in star mode
/V1/faq/products/:id/questions GET anonymous Product questions (public projection)
/V1/faq/categories/:id/questions GET anonymous Category questions (public projection)
/V1/faq/questions/search GET anonymous Full-text search (public projection)
/V1/faq/categories/url-key/:key/store/:id GET anonymous Category lookup by URL key
/V1/faq/questions/url-key/:key/store/:id GET anonymous Question lookup by URL key (public projection; 404 for non-public questions)
/V1/faq/questions/:id/notify POST admin (Magendoo_Faq::question_edit) Send answer notification email
/V1/faq/questions/:id/view POST anonymous Increment view count
/V1/faq/tags GET admin (Magendoo_Faq::question) List tags (SearchCriteria)
/V1/faq/tags/:id GET admin (Magendoo_Faq::question) Get tag
/V1/faq/tags POST admin (Magendoo_Faq::question_edit) Create tag
/V1/faq/tags/:id PUT admin (Magendoo_Faq::question_edit) Update tag
/V1/faq/tags/:id DELETE admin (Magendoo_Faq::question_delete) Delete tag

Notes:

  • Anonymous routes return a public projection (PublicQuestionInterface): title, url_key,
    answers, position, rating counters, view count and SEO fields. They never include
    sender_name, sender_email, customer_id, status or visibility.
  • /V1/faq/questions/submit requires an authenticated customer. The storefront form is
    protected by reCAPTCHA through a frontend predispatch observer — an event REST never
    dispatches — so an anonymous REST route would bypass the captcha entirely. Guests submit
    through the storefront form. The submit service enforces the same rules on both entry
    points: guest permission, e-mail validation, GDPR consent, slug sanitisation.
  • Rating identity (customer id / IP) is resolved server-side and cannot be supplied by the
    caller.

For Developers

Service contracts

Api/
├── CategoryRepositoryInterface      # Category CRUD + getByUrlKey()
├── QuestionRepositoryInterface      # Question CRUD + getByUrlKey() (ACL-protected, full object)
├── QuestionManagementInterface      # submit, rate, search, product/category listings,
│                                    # getQuestionByUrlKey() (public projection), notify, views
└── TagRepositoryInterface           # Tag CRUD

Repositories support the SearchCriteria pattern. Storefront-safe reads go through
QuestionManagementInterface and return Api\Data\PublicQuestionInterface.

Database schema (12 tables)

  • magendoo_faq_category, magendoo_faq_question, magendoo_faq_tag — entities
    (questions carry SEO fields, rating counters, view count and the GDPR consent record:
    consent_given_at + consent_text snapshot)
  • magendoo_faq_category_store, magendoo_faq_question_store — store assignment
  • magendoo_faq_category_customer_group, magendoo_faq_question_customer_group — group restrictions
  • magendoo_faq_question_category, magendoo_faq_question_product, magendoo_faq_question_tag — M:N relations
  • magendoo_faq_rating — individual votes (dedup by customer id / IP, server-resolved)
  • magendoo_faq_search_log — one row per executed search

url_key is unique on both the category and question tables.

Frontend

Layout handles: faq_index_index, faq_category_view, faq_question_view,
faq_question_search, faq_tag_view, plus catalog_product_view for the product tab.

RequireJS components: faqAskForm, faqRating, faqAutocomplete, faqTabDeeplink.

All storefront blocks implement IdentityInterface, so saving, deleting or re-publishing FAQ
content invalidates the affected full-page-cache entries automatically. The search results
page is deliberately non-cacheable so the search log sees repeat searches.

Admin

Menu: Content → FAQ (FAQ Categories, FAQ Questions, FAQ Search Terms).

UI components: faq_category_listing, faq_category_form, faq_question_listing,
faq_question_form, faq_searchlog_listing. Mass actions: delete and change-status on both
grids, plus change-visibility for questions.

ACL (etc/acl.xml):

Magendoo_Faq::faq                       # Content → FAQ menu
├── Magendoo_Faq::category              # Manage Categories (grid access)
│   ├── Magendoo_Faq::category_view
│   ├── Magendoo_Faq::category_edit
│   └── Magendoo_Faq::category_delete
├── Magendoo_Faq::question              # Manage Questions (grid access)
│   ├── Magendoo_Faq::question_view
│   ├── Magendoo_Faq::question_edit
│   ├── Magendoo_Faq::question_delete
│   └── Magendoo_Faq::question_approve
└── Magendoo_Faq::search_log            # Search Terms Report
Magendoo_Faq::config                    # System configuration section (under Stores)

CMS widgets

Widget Description
FAQ Questions List (magendoo_faq_questions_list) Questions, optionally filtered by category; list or accordion template
FAQ Categories List (magendoo_faq_categories_list) Category links with optional question counts
FAQ Search Box (magendoo_faq_search_box) Search form with configurable placeholder

SEO implementation

  • JSON-LD: FAQPage schema on category and question pages, encoded with
    JSON_HEX_TAG|JSON_HEX_AMP|JSON_HEX_APOS|JSON_HEX_QUOT so user-supplied titles cannot break
    out of the script block.
  • Sitemap: two Magento\Sitemap\Model\ItemProvider\ItemProviderInterface providers
    (categories + questions), honouring per-entity exclude_sitemap.
  • Canonical: Block\Faq\Canonical emits <link rel="canonical"> on the FAQ home,
    category, question and tag pages when seo/use_canonical is enabled; a per-entity
    canonical_url field overrides the computed URL.
  • Robots: per-entity noindex/nofollow, and a configurable robots meta on search results.
  • Hreflang: alternate links per assigned store when seo/hreflang_enabled is on.

Extending

<!-- app/code/Vendor/Module/etc/di.xml -->
<type name="Magendoo\Faq\Api\QuestionManagementInterface">
    <plugin name="vendor_custom_question_logic"
            type="Vendor\Module\Plugin\QuestionManagementPlugin"/>
</type>

All service contracts are interfaces resolved through DI preferences, so they can be plugged
or overridden the standard Magento way.

Tests and quality gates

  • 85 PHPUnit unit tests (Test/Unit, PHPUnit 12): composer test or
    vendor/bin/phpunit -c phpunit.xml.dist
  • PHPStan (level in phpstan.neon.dist, pre-existing findings recorded in
    phpstan-baseline.neon): composer analyse
  • phpcs with the Magento2 standard: composer cs
  • CI runs all three on PHP 8.1–8.4.

Known limitations

Stated plainly so you don't have to discover them:

  • The "Show Full Answer" and "Hide Direct URL" checkboxes on the question form are
    stored but not yet consumed by any storefront code — they currently have no effect.
  • The Search Terms report logs one row per executed search; the Hits column is always 1.
    Sort by Search Query to gauge frequency.
  • Only an en_US translation ships (i18n/en_US.csv, all 269 phrases).
  • Anonymous REST submission is intentionally disabled (see the REST notes above).

FAQ

Q: Can customers submit questions without an account?

Yes — through the storefront form, when magendoo_faq/general/allow_guest_questions is on.
Enable reCAPTCHA for the form to keep bots out. The REST submit route, by contrast, requires a
logged-in customer token.

Q: Can I migrate FAQs from another platform?

Yes, via bin/magento magendoo:faq:import with a CSV whose header row matches the database
column names (run an export first to get a template), or via the admin REST API.

Q: Can I restrict FAQs to specific customer groups?

Yes. Both categories and questions have a Customer Groups multiselect; an empty selection means
visible to all groups. Restrictions are enforced on listings, question pages and the anonymous
REST reads.

Q: What rating modes are available?

yes_no ("Was this answer helpful?"), voting (thumbs up/down with counts) and
average_rating (a real 1–5 star input; average_rating stores the 0–5 mean).

Q: Does the module support multiple languages/stores?

Content is assignable per store view, and hreflang tags can be enabled. UI translations
currently ship for en_US only — add your own CSV under i18n/.

Troubleshooting

FAQ URLs return 404 — run bin/magento magendoo:faq:reindex (purges and rebuilds all FAQ
rewrites), check the URL prefix doesn't collide with a CMS page, and flush cache.

Questions missing on the storefront — the question must have status Answered, visibility
Public (or Logged In while testing as a customer), a store assignment matching the current
store view, and no excluding customer-group restriction.

Email notifications not sent — enable magendoo_faq/user_notifications/enabled (customer
"your question was answered" mail) and/or magendoo_faq/admin_notifications/enabled (new
question alert); both default to off. The "Save and Send Email" button reports exactly what
happened — including "notifications are disabled".

reCAPTCHA not validating — configure keys and pick a type for "Enable for FAQ Ask a
Question Form" under Stores → Configuration → Security → Google reCAPTCHA Storefront.

Changelog

See CHANGELOG.md.

License

MIT — see the LICENSE file.

Contributing

Pull requests are welcome. Please follow the Magento2 coding standard
(composer cs), keep PHPStan green against the baseline (composer analyse), and add unit
tests for behaviour changes (composer test).

Support & Resources

Changelog

All notable changes to this project will be documented in this file.

The format follows Keep a Changelog.
This project adheres to Semantic Versioning.

Unreleased

[2.0.1] - 2026-07-25

Fixed

  • Exported tag names can now be re-imported. magendoo:faq:export writes tags as names,
    but nothing mapped them back to ids, so the name reached the junction table, cast to 0
    and violated the foreign key — aborting the whole row. A plain export → import round trip
    failed on any question that had tags. Import now resolves names to ids case-insensitively
    and creates any tag that does not exist, which also gives merchants the only bulk way to
    add tags, since the module has no admin screen for creating them. Generated URL keys are
    deterministic and de-duplicated, so importing the same file twice changes nothing.
  • The junction writer now drops anything that is not a positive integer instead of turning it
    into a row that cannot exist, so no future caller can reproduce this class of failure.

2.0.0 - 2026-07-25

A review-and-hardening release: 43 fixes across routing, security, caching, the admin surface,
email and the REST API, plus the first real test suite. Several 1.0.0 features that could not
work at all (ratings, tags, customer-group restrictions, admin notifications, canonical links,
grid delete, icon upload) now do.

Upgrade notes — required steps

bin/magento setup:upgrade          # new columns, unique url_key, new foreign keys
bin/magento magendoo:faq:reindex   # purge and regenerate ALL FAQ URL rewrites
bin/magento cache:flush

The reindex is mandatory: 1.0.0 wrote question rewrites against a literal store_id = 0
(which the router never matches — every question URL saved with "All Store Views" was a 404)
and never deleted rewrites for removed entities. The command now purges and rebuilds, which
repairs both. setup:upgrade will fail if existing rows violate the new url_key unique
constraint — deduplicate url_keys first on stores with hand-edited data.

Breaking changes

  • API — QuestionManagementInterface::rateQuestion() drops the $customerId and
    $ipAddress parameters (they let any caller stuff votes and attribute them to real
    customers). New signature: rateQuestion(int $questionId, string $voteType). Voter identity
    is resolved server-side; extra request fields are ignored on the REST route.
  • API — submitQuestion() now takes a bool $gdprConsent argument and returns the new
    PublicQuestionInterface projection.
  • REST — anonymous responses no longer leak asker PII. getProductQuestions(),
    getCategoryQuestions(), searchQuestions() and the question url-key lookup return the
    public projection: sender_name, sender_email, customer_id, status and visibility
    are gone from anonymous payloads. The url-key route is now served by
    getQuestionByUrlKey() and returns 404 for non-public questions.
  • REST — POST /V1/faq/questions/submit requires an authenticated customer. The
    anonymous route bypassed reCAPTCHA (and every other submit rule) entirely; guests submit
    through the protected storefront form.
  • average_rating changes meaning from percent-positive to a 0–5 star average. The
    "Average Rating" mode is now a real 1–5 star input; on the wire the star value travels in
    voteType as "1""5". (No stored data is affected in practice: 1.0.0 ratings always
    failed against a nonexistent table.)
  • Removed config fields (they were read by no code): general/add_to_toolbar,
    general/add_to_footer, the entire home_page group (use_cms_page, cms_page_id,
    layout), navigation/show_ask_button, navigation/include_categories_in_search,
    product_page/short_answer_behavior and seo/rich_breadcrumbs.
  • Schema: url_key is now unique on the category and question tables; the customer-group
    junction tables gained foreign keys.
  • Licence headers corrected from OSL-3.0 to MIT, matching the LICENSE file and
    composer.json the project has always shipped. A correction of a contradiction, not a
    relicensing. composer.json no longer hardcodes a version (tags are the source of truth).

Fixed — URLs and SEO

  • Question URL rewrites are written against real store ids; saving with "All Store Views"
    no longer produces dead store_id = 0 rows (every question URL 404'd on a stock install).
  • magendoo:faq:reindex no longer un-scopes every rewrite (it now looks up each entity's
    store assignment) and no longer aborts on the first url_key collision — it skips and reports.
  • URL rewrites are deleted when an entity is deleted or unpublished; url_key collisions
    produce an actionable error naming the conflicting path instead of "Something went wrong".
  • The router verifies category/question membership on two-segment URLs (previously any
    category paired with any question served a duplicate 200) and redirects suffix and
    trailing-slash variants to the canonical form.
  • A canonical <link> is actually emitted (gated by seo/use_canonical, with the per-entity
    override) — in 1.0.0 the config and entity fields had no frontend consumer.
  • Hreflang alternates respect per-store prefixes, entity store assignment and module
    enablement instead of gluing the current path onto every store's base URL.
  • JSON-LD is hex-escaped so a shopper-submitted question title containing </script> can no
    longer break out of the structured-data block (stored XSS).

Fixed — features that had no working write path

  • Ratings: votes are recorded against the real magendoo_faq_rating table — every vote in
    1.0.0 failed with "Base table not found" and the raw SQL error was shown to the shopper.
    Guest-rating enforcement now actually applies.
  • Tags: the question form gained a Tags multiselect and save path — the tag cloud, tag
    pages and router tag branch previously read a junction nothing could populate.
  • Customer-group restrictions: both admin forms gained Customer Groups multiselects, and
    the storefront listings, question page and anonymous REST reads all enforce them.
  • Admin notifications: sendAdminNotification() is now called after a successful
    submission (form and REST) — it previously had no caller.
  • GDPR consent is recorded: consent_given_at (UTC) plus a snapshot of the exact consent
    wording shown, displayed read-only on the admin form. The consent checkbox label also
    renders again (it was written to one config path and read from another).

Fixed — caching

  • Storefront blocks implement IdentityInterface, so answering, editing or unpublishing a
    question invalidates the affected full-page-cache pages (previously changes were invisible
    until a manual flush).
  • Removed the hour-long block cache on the FAQ home block that served one customer group's
    content to all groups.
  • The ask form no longer reads the customer session on a cacheable page (it gates
    client-side), so "please log in" can no longer get baked into the logged-in cache variant.
  • Search results are non-cacheable so the search log counts repeat searches.

Fixed — admin surface

  • Grid row Delete and the edit-page Delete buttons work (they issued GETs against POST-only
    controllers and 404'd).
  • Grids are no longer empty for non-superuser roles (the listings referenced ACL ids that
    did not exist).
  • Category icon upload works: the missing faq/category/upload controller now exists, the
    form shows the stored image, saving without a new upload no longer wipes the icon, and the
    storefront renders the proper media URL.
  • "Save and Send Email" reports what actually happened (sent / notifications disabled /
    transport failure) instead of always claiming success.
  • A failed save restores everything the admin typed (the data persistor is now populated).
  • Opening an edit form runs a constant number of queries instead of 3N+1 over the whole table.

Fixed — email and submission

  • The answer notification sends to exactly one validated recipient (a shopper-controlled
    field was previously split on commas into a recipient list), renders in the correct area,
    and resolves the store from the question's own assignment.
  • The submit success redirect uses the validated referer helper instead of the raw
    Referer header (open redirect).
  • The reCAPTCHA observer takes request/response from DI — enabling reCAPTCHA (the module's
    own recommendation) previously fataled every submission with HTTP 500.
  • Storefront form and REST submit share one server-side rule set (guest permission, email
    validation, GDPR consent, slug sanitisation); caller-supplied answers, counters and
    customer ids are ignored.

Fixed — data layer and performance

  • Export carries the relation columns it silently dropped and defuses spreadsheet formula
    injection; import reports unknown ids instead of quietly creating duplicates.
  • The tag cloud collapses from unbounded per-tag queries to one bounded query; the categories
    widget from N+2 queries to one; search uses the declared FULLTEXT index instead of a
    double LIKE scan.

Added

  • Unit test suite: 85 PHPUnit 12 tests concentrated on the previously broken logic
    (URL-rewrite store scoping, JSON-LD escaping, rating integrity, config getters).
  • CI now runs phpcs, PHPStan (with a maintained baseline) and the unit suite on PHP 8.1–8.4;
    fork PRs no longer fail on unavailable marketplace credentials.
  • Complete en_US.csv translation (269 phrases; 1.0.0 shipped 31).
  • Hard dependencies declared: Magento_Sitemap, Magento_Widget and the reCAPTCHA modules.

Removed

  • Fabricated README/marketing claims (Playwright coverage, unreleased versions 1.1.0/1.2.0).
  • The dead config fields listed under breaking changes.

1.0.0 - 2026-04-13

Added

  • SEO-optimized FAQ home page, category pages, and question pages with configurable URL prefix and suffix
  • Custom frontend router for faq/{category-slug}/{question-slug} URL structure
  • Product Questions tab on product detail pages (configurable tab name and position)
  • Ask-a-Question form on product pages with guest/logged-in support and GDPR consent checkbox
  • Answer helpfulness rating system (Yes/No, Voting, and Average Rating modes)
  • Social share buttons on question pages (Facebook, Twitter, LinkedIn, Pinterest, Email)
  • Admin grids and forms for FAQ Categories and FAQ Questions with full CRUD
  • WYSIWYG editor for full answers; short answer field for listing previews
  • Question workflow: Pending → Answered → Rejected status transitions
  • Question visibility: Public, Logged-in only, Hidden
  • 12 database tables: categories, questions, tags, ratings, search log, and M:N junction tables for stores, products, customer groups
  • REST API: category CRUD (/V1/faq/categories), question CRUD (/V1/faq/questions), product questions, category questions, search, submit, rate
  • URL rewrites generated on category/question save for SEO tool compatibility
  • FAQPage JSON-LD structured data on category and question pages
  • XML sitemap integration via ItemProviderInterface
  • Hreflang tag support for multi-store setups
  • Breadcrumbs with Home > FAQ > Category > Question hierarchy
  • FAQ search with search terms report in admin
  • Tag system with tag cloud and tag pages
  • Three FAQ Widgets: Questions List, Categories List, Search Box
  • Per-entity robots meta tag (noindex/nofollow) override
  • Email notifications: admin notified on new question, customer notified on answer
  • Customer group visibility restrictions on categories and questions
  • Admin system configuration under Stores > Magendoo Extensions > FAQ and Product Questions
  • CLI command magendoo:faq:reindex to regenerate URL rewrites
  • i18n/en_US.csv translation file
Versions
Version Stability QA Status Compatibility Released
v2.0.1 stable Not tested Not yet tested Details 2026-07-25 19:25:36
v2.0.0 stable Not tested Not yet tested Details 2026-07-25 15:22:47
1.0.0 stable Fail Magento 2.4.7-2.4.9 Details 2026-04-13 13:29:06

Requires 9

Package Constraint
magento/framework >=103.0.0
magento/module-backend >=102.0.0
magento/module-catalog >=104.0.0
magento/module-cms >=104.0.0
magento/module-customer >=103.0.0
magento/module-store >=101.0.0
magento/module-ui >=101.2.0
magento/module-url-rewrite >=102.0.0
php >=8.1

Requires-dev 2

Package Constraint
phpstan/phpstan ^1.10 || ^2.0
phpunit/phpunit ^10.5

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.

Compatibility matrix (Magento × PHP)
Magento PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
2.4.7 Pass Pass
2.4.8 Pass Pass
2.4.9 Pass Pass

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.

Static analysis results
Tool Status Findings Summary
PHPCS Warning 139 139 warnings (ruleset: Magento2), 86 auto-fixable with phpcbf
PHPMD Warning 77 77 rule violations (UnusedPrivateField:77)
Cpd Warning 3 3 duplicated chunks spanning 257 total lines (min-lines=5, min-tokens=70)
Composer validate Info 9 valid; 9 advisory notes (composer validate --strict)

PHPStan

Type-checks the module's PHP against a real Magento install at the configured gate level. Re-runs per Magento and PHP version because resolvable symbols differ between releases.

PHPStan results by Magento and PHP version
Magento PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
2.4.7 49 49
2.4.8 49 49
2.4.9 49 49

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

Unit tests results by Magento and PHP version
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

Integration tests results by Magento and PHP version
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.

Security results
Tool Status Findings Summary
Composer audit Pass 0
Malware scan Pass 0
License
MIT
Authors
  • Magendoo

More from magendoo

View vendor
Make it pay

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.