hryvinskyi / magento2-invisible-captcha
Magento 2 Google Invisible Captcha (Recaptcha) module
Hryvinskyi_InvisibleCaptcha
Multi-provider invisible captcha and bot protection for Magento 2.
Version 3 merges the former Hryvinskyi_TurnstileProtection module into a single,
provider-agnostic extension that protects both individual forms and whole
routes, using whichever CAPTCHA provider you configure.
Providers
| Code | Provider | Mode |
|---|---|---|
recaptcha_v2_checkbox |
Google reCAPTCHA v2 — "I'm not a robot" | visible checkbox |
recaptcha_v2_invisible |
Google reCAPTCHA v2 — invisible badge | invisible |
recaptcha_v3 |
Google reCAPTCHA v3 | invisible, score-based |
recaptcha_enterprise |
Google reCAPTCHA Enterprise | invisible, score-based (assessments API) |
turnstile |
Cloudflare Turnstile | managed / invisible |
Capabilities
- Form protection — invisibly protects the same surface as Magento's native
reCAPTCHA suite: customer login (incl. AJAX popup & checkout), register,
forgot password, account edit, contact, newsletter,
send-to-friend, product review, share wishlist, apply coupon
(cart), PayPal Payflow Pro, and the admin login / forgot-password
forms. Each is toggled independently; score-based providers also support a
per-form threshold. - WebAPI / headless protection — REST and GraphQL validation for
place order, apply coupon (checkout), in-store pickup place order,
and resend confirmation email. Protected endpoints require the captcha
token in theX-Captcha-Tokenrequest header (X-ReCaptchaalso accepted).
AhryvinskyiInvisibleCaptchaConfig(formType:)GraphQL query exposes the
active provider's client config for headless storefronts. Endpoint coverage is
extensible viaApi\Webapi\WebapiConfigProviderInterface. - Route protection — a full-page interstitial challenge for requests that
match a Cloudflare-style rule expression (field / operator / value rows joined
by AND/OR). A verified visitor receives an HMAC-signed cookie and is keyed into
a separate Varnish cache bucket via the HTTP context. An optional fallback
provider is revealed on the challenge page after a delay. The interstitial is
a self-contained, system-font page (no external font/CDN requests) whose accent
color is themeable from the admin. - robots.txt-aware gating — the rule field Blocked by robots.txt
(robots_txt_blocked eq 1) challenges every request for a URL your
robots.txt disallows. The served robots.txt is resolved per website — a
physicalpub/robots.txtwins, otherwise the Search Engine Robots custom
instructions — and evaluated with RFC 9309 semantics: user-agent group
selection with*fallback, longest-match precedence,Allowwinning
specificity ties,*wildcards and$end anchors. Legitimate crawlers
never fetch disallowed URLs (and can stay in Excluded User Agents), so the
rule surfaces exactly the bots that ignore robots.txt. Missing or empty
robots.txt never challenges (fail-safe). - Rule tester — a Test Rules panel under the rules editor simulates any
storefront request (URL or path, method, User-Agent, client IP, referer,
store view) against the rules as currently edited, before saving, and
reports whether it would pass or be challenged: per-condition ✓/✗ trace with
the actual field values, bypass reasons (excluded IP / user agent, verify
endpoint), scope context (protection disabled, provider unconfigured) and
warnings. The simulation runs under full store emulation; the full action
name is auto-resolved through URL rewrites (SEO URLs, one redirect hop) and
the front-name → route-id map, and can be overridden manually.
Installation
composer require hryvinskyi/magento2-invisible-captcha
bin/magento module:enable Hryvinskyi_InvisibleCaptcha
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento setup:static-content:deploy
Dependencies: hryvinskyi/magento2-base, hryvinskyi/magento2-theme-assets,
hryvinskyi/magento2-configuration-fields, symfony/http-client + PSR-7/17/18.
Configuration
Stores → Configuration → Hryvinskyi → Invisible Captcha & Bot Protection
(hryvinskyi_invisible_captcha/*):
- General — master switch, active provider, lazy-load, disable-submit, debug.
- Provider Credentials — site/secret keys (+ Enterprise project id, widget
options) per provider. Secret keys are encrypted and flagged sensitive. - Form Protection — per-form toggles and score thresholds (storefront + admin).
- Route Protection — rules editor, route-gate provider override, fallback
provider, cookie lifetime, IP / user-agent exclusions, AJAX-marker params, etc.
The Ignored Filter Params, AJAX Marker Params and Background AJAX Marker
Params lists use tag-style inputs (viahryvinskyi/magento2-configuration-fields);
values are still stored newline-separated, so existing data is untouched.
Includes a Challenge Page Appearance sub-group that themes the interstitial
accent palette —primary_color(#2f6bd8),primary_color_deep(#2557b6) and
primary_color_soft(rgba(47,107,216,0.12)) under
hryvinskyi_invisible_captcha/route_protection/appearance/*, injected as
CSP-safe--primary*overrides and configurable per store view. - Advanced — outbound verification HTTP timeout.
Architecture (extension points)
Api\Provider\ProviderInterface+ProviderPoolInterface— add a provider by
implementing the interface and adding it to theModel\Provider\PoolDI array.Api\Verification\VerifierInterface/VerificationRequestInterface/
VerificationResultInterface— provider-agnostic verification.Api\Filter\FieldProviderInterface/OperatorProviderInterface— extend the
route rule engine with custom fields/operators via DI arrays. Optionally
implementApi\Filter\OperatorMetadataInterface(value shape: text /
text_required / list / pattern / number) and
Api\Filter\FieldValueHintInterface(format pattern, message, placeholder)
to plug into the editor's dynamic value validation.Api\RobotsTxt\{SourceInterface, ParserInterface, MatcherInterface}— swap
where robots.txt content comes from, how it is parsed, or how rules are
matched (all bound viadi.xmlpreferences).Model\Strategy\{Token,Failure}\*— pluggable token extraction & failure handling.
WebAPI / checkout note
The place_order, coupon_code (checkout), store_pickup and
resend_confirmation_email keys validate server-side on the WebAPI/GraphQL
call. They are off by default. Before enabling one, make sure your storefront
/ headless client sends the captcha token in the X-Captcha-Token header on that
call (for score-based providers, execute the provider with the matching action,
e.g. place_order) — otherwise those requests will be rejected. The token for
form-level (non-WebAPI) submissions is handled automatically via the hidden
hryvinskyi_invisible_token field.
Verify endpoint & caching
The route-gate challenge POSTs to invisiblecaptcha/verify. On success it sets the
hryvinskyi_captcha_verified cookie and the CAPTCHA_VERIFIED HTTP-context vary
key. After deploying v3, run a full page cache flush (the vary key / cookie /
X-InvisibleCaptcha-Challenge header were renamed from the old Turnstile names).
Upgrading from v2.x / TurnstileProtection
Run setup:upgrade. The data patch MigrateLegacyCaptchaConfig copies your old
hryvinskyi_invisible_captcha/* (reCAPTCHA v3) and hryvinskyi_turnstile/*
settings into the new tree (encrypting the legacy plaintext v3 secret). The
standalone Hryvinskyi_TurnstileProtection module is removed — disable it after
upgrading.
Migrating from Magento's native Google reCAPTCHA
If the store already uses Magento's built-in Google reCAPTCHA (Magento_ReCaptcha*),
import its configuration into this module with:
bin/magento hryvinskyi:invisible-captcha:migrate-recaptcha --dry-run # preview the change set
bin/magento hryvinskyi:invisible-captcha:migrate-recaptcha # apply
The command reads every recaptcha_frontend/* and recaptcha_backend/* row from
core_config_data (all scopes) and writes the equivalent
hryvinskyi_invisible_captcha/* settings:
- Credentials — v3, v2-checkbox and v2-invisible site/secret keys map to the
matching provider under Provider Credentials. The native module stores both
keys encrypted; the secret key is copied verbatim (this module's field is
encrypted too, so the ciphertext stays valid under the same crypt key), while the
site key is decrypted on copy because this module stores it in plain text.
Run the command on the installation that owns the crypt key. Frontend keys win
when frontend and backend differ. - Per-form selectors — each
type_for/<form>that isn't "disabled" turns on the
corresponding form under Form Protection (the nativeplace_ordergate enables
both checkout and in-store pickup here). The v3 section-wide score threshold is
fanned out to each enabled v3 form. - Derived — the active provider (most-used across the enabled forms), the master
switch and the form-protection switches are set so protection stays live. - Status hand-over — after copying, each migrated native
type_for/*selector
row is cleared, so the built-in reCAPTCHA stops challenging those forms and
this module takes over in the same run (source_disabledin the summary table).
Native credentials are left in place, so the switch is easy to revert.
Existing values in the target tree are never overwritten unless you pass --force.
The command flushes the config cache and prints a per-path summary table.
Upgrading from a version whose migration copied the site key without decrypting
it? Re-run with--forceto overwrite the broken ciphertext values.
skipped_undecryptablerows / missing site keys? The installation's crypt key
(app/etc/env.php) cannot decrypt those values — typical when the database was
imported from another environment without its crypt key. Nothing else encrypted
(payment keys, SMTP passwords) will decrypt on such an installation either.
Restore the original crypt key (all versions, in order) or re-enter the keys in
the admin, then re-run the command.
Because the hand-over disables the native reCAPTCHA at config level, physically
removing its modules is optional. If you still want them gone, the companion
metapackage hryvinskyi/magento2-invisible-captcha-recaptcha-replace replaces
every magento/module-re-captcha-* package (Two-Factor Auth and security.txt
from magento/security-package are left untouched). Caution: other installed
code may compile against the reCAPTCHA API modules — notably the bundled
PayPal Braintree module implements
Magento\ReCaptchaWebapiApi\Api\WebapiValidationConfigProviderInterface, so
removing all 25 packages breaks setup:di:compile on standard installs. On such
stores prefer the config-level disable above.
CLI
bin/magento hryvinskyi:invisible-captcha:disable [global|frontend|adminhtml] [--website_id=N]
bin/magento hryvinskyi:invisible-captcha:migrate-recaptcha [--dry-run] [--force]
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog,
and this project adheres to Semantic Versioning.
[3.1.0] - 2026-07-11
Added
Blocked by robots.txtroute-protection rule field. The Protection Rules
editor gains a numeric fieldrobots_txt_blockedthat resolves to 1 when the
requested URL is disallowed by the website's robots.txt — so the single rule
robots_txt_blocked eq 1challenges every client that ignores robots.txt,
while legitimate crawlers (which never fetch disallowed URLs, and typically
sit in Excluded User Agents anyway) are unaffected. The served robots.txt
is resolved per website: a physicalpub/robots.txtwins (the web server
delivers it directly), otherwise the Search Engine Robots custom
instructions — the same website-scoped configMagento_Robotsrenders at
/robots.txt. Evaluation follows RFC 9309 / Google semantics: stacked
User-agentgroups with the most specific agent token winning and*as
fallback, tied groups merging, longest-path-match precedence withAllow
winning specificity ties,*wildcards and$end anchors, comment/BOM
stripping, and lenient relative-path normalization. Content and parsed rules
are memoized per request; missing, empty or unreadable robots.txt never
challenges (fail-safe). The field introduces a first-class boolean rule
type: in the builder the operator list narrows to equals / does not equal
and the Value cell renders as a strict Yes/No select (persisted as1/0,
so the raw-expression form staysrobots_txt_blocked eq 1). Extensible via
the newApi\RobotsTxt
SourceInterface/ParserInterface/MatcherInterfacepreferences.
Covered by unit tests (parser, matcher, source, field) and a
RouteProtection\RobotsTxtRuleTestintegration scenario.- Tag-style editors for the route-protection parameter lists. Ignored
Filter Params, AJAX Marker Params and Background AJAX Marker Params are
now tag inputs (type a name, press Enter) rendered by the TagList field from
the newhryvinskyi/magento2-configuration-fieldsdependency, wired as
RouteParamsTagListvirtual types. Values are still stored
newline-separated, so existing configuration keeps working unchanged. - "Test Rules" panel — simulate a request against the Protection Rules.
The rules editor gains a collapsible tester (collapsed by default, the
header toggles it) that answers "would this page/request pass or hit the
rules?" without leaving the admin: enter a URL (absolute or path), pick the
store view and method, optionally set User-Agent, client IP and referer,
and run. The verdict distinguishes CHALLENGE (expression
matched and the gate would fire), MATCHED — but no challenge (with the
reasons: excluded IP/user agent, verify endpoint, protection disabled,
provider unconfigured) and PASS, and shows a per-condition ✓/✗ trace
with each field's actual resolved value plus a full field-value snapshot.
The tester evaluates the draft rules currently in the editor (unsaved
changes included; falls back to the saved config), runs server-side under
full store emulation (Magento\Store\Model\App\Emulation) so store-scoped
rules, exclusion lists, credentials and robots.txt resolve exactly as on
the live storefront, and rebuilds the real field pool around a synthetic
request — the same field implementations the gate uses, no duplicated
value logic. The full action name is resolved best-effort through URL
rewrites (SEO URLs, one redirect hop honored) and the front-name →
route-id map, with a manual override and an explicit warning when
action_nameconditions couldn't be grounded. Under the hood this release
also extracts the exclusion checks intoApi\ExclusionPolicyInterface
(shared by the live gate and the tester) and adds
Api\ExpressionTracerInterface— a no-short-circuit diagnostic twin of the
evaluator. New admin POST endpointhryvinskyi_invisible_captcha/tester/run
(ACLHryvinskyi_InvisibleCaptcha::config); new dependencies
magento/module-backendandmagento/module-url-rewrite. - Dynamic value validation and per-field placeholders in the rules editor.
Every operator now declares the shape of value it consumes via the new
optionalApi\Filter\OperatorMetadataInterface—text(equals; empty is
legal),text_required(contains / starts with / ends with — empty never
matches, so it's flagged),list(is in list — one or more comma/space
separated items),pattern(regex operators) andnumber(relational
operators) — and fields can expose a format hint via the new optional
Api\Filter\FieldValueHintInterface(anchored pattern + message +
placeholder). The Value cell validates live as you type: numbers for
numeric comparisons, per-item checks for lists, regex compilation for
pattern operators (bare and~…~i-delimited forms), and full-value format
checks for exact-match operators on hinted fields — Client IP requires a
valid IPv4/IPv6 literal, HTTP Method a method token, Full Action Name a
catalog_product_view-style identifier. Fragment-taking operators
(contains, regex, …) deliberately skip format hints. Invalid values get a
red state plus an inline message; validation is advisory (the evaluator
stays fail-safe) but catches conditions that would silently never match.
All eleven built-in fields now ship example placeholders instead of the
generic "Value". Third-party operators/fields without the new optional
interfaces keep working unchanged (default: free text, no hint). - Tag input for the list operators. With is in list / is not in list
selected, the Value cell renders each item as a removable chip: Enter,
comma, space, paste or blur commits the typed text (pasted content is
split on commas/whitespace, duplicates are skipped), Backspace on an empty
input pops the last chip, and items failing the field's format hint (e.g.
a malformed IP on Client IP) are highlighted individually. The canonical
comma-separated string lives in a hidden input with the row's form name,
so persistence, the parser's/[\s,]+/split, raw-expression mode and the
rule tester all see exactly what a plain text input would have produced.
Switching between list and scalar operators swaps the control in place.
[3.0.4] - 2026-07-08
Fixed
- Catch-all route rules deadlocked verification. The route gate also fired on
its owninvisiblecaptcha/verifyendpoint, so with a rule broad enough to match
it (e.g.action_name regex .*) the challenge page's token POST was answered
with another 403 challenge and a visitor could never pass.RequestCheckernow
always exempts the verify path (same normalization as the router); a direct GET
on the endpoint now yields a plain 404 instead of a challenge.
[3.0.3] - 2026-07-08
Fixed
- Route-protection regex rules accept bare (Cloudflare-style) patterns. The
regex/not_regexoperators passed the admin value straight to
preg_match(), which requires PCRE delimiters — so a natural pattern like
.*or^catalog_.*errored out ("invalid regex skipped" in the log) and the
rule never fired. Values are now tried as delimited PCRE first (modifiers like
~…~ikeep working) and auto-wrapped in~…~on a parse error; only patterns
invalid in both forms are skipped (still fail-safe, still logged).
Changed
migrate-recaptcha: undecryptable values are reported, not silently skipped.
When a native encrypted value cannot be decrypted with this installation's crypt
key (e.g. a DB imported from another environment), the row now appears in the
change table with the newskipped_undecryptablestatus and the command prints
a crypt-key warning with remediation steps, instead of omitting the row without
a trace.
[3.0.2] - 2026-07-08
Fixed
- Login-failure redirect never used the before-auth URL.
RedirectUrl\BeforeAuthUrl
was typed againstSessionManagerInterface, whose global preference is
Session\Generic— a session object with the default storage namespace, while
before_auth_urlis written into the customer namespace. The provider therefore
always fell back to the login URL. It now depends onMagento\Customer\Model\Session
(Proxy-wired inetc/frontend/di.xml), so a failed login challenge redirects back
to the customer's intended page again. - Unit test suite runs green end-to-end on PHP 8.5 / PHPUnit 10 (598 tests): fixed a
by-reference capture bug inRouteGateTest, seeded the global ObjectManager for the
backend block inProtectionRulesTest, avoided the undefinedBPconstant in
DebugTest, and re-pointedBeforeAuthUrlTestat the concrete customer session
(mocking the magic accessor on the interface fatally failed under PHPUnit 10).
[3.0.1] - 2026-07-08
Fixed
migrate-recaptcha: native site key was copied as ciphertext. Magento's
native reCAPTCHA storespublic_keythroughConfig\Backend\Encrypted, while
this module keepssite_keyin plain text — the migration now decrypts values
carrying the Magento crypt envelope (N:N:…) and passes legacy plaintext keys
through untouched; undecryptable values are skipped instead of written. Stores
that migrated with the broken version can repair by re-running with--force.
Added
migrate-recaptcha: status hand-over. After copying, the command clears
every migrated nativetype_for/*selector row (newsource_disabledchange
status, previewed in--dry-run), so the built-in reCAPTCHA is disabled and
this module is enabled in one run. Native credentials are left in place;
physically removing the native modules becomes optional — which also avoids
setup:di:compilebreakage on installs whose bundled code (e.g. PayPal
Braintree) compiles against the reCAPTCHA API modules.
[3.0.0] - 2026-07-08
Major rewrite into a multi-provider captcha & bot-protection module. The
former Hryvinskyi_TurnstileProtection module is merged in and removed.
Added
-
Five providers behind one abstraction (
Api\Provider\ProviderInterface+
ProviderPoolInterface): Google reCAPTCHA v2 checkbox, v2 invisible, v3,
reCAPTCHA Enterprise, and Cloudflare Turnstile. Pick the active provider in
Stores → Configuration → Hryvinskyi → Invisible Captcha & Bot Protection. -
Route-level protection (capability B, from TurnstileProtection): a
site-wide interstitial challenge driven by a Cloudflare-style rule engine
(Model\Filter\*), an HMAC-signed verified cookie, Varnish HTTP-context vary,
and a verify endpoint atinvisiblecaptcha/verify. -
reCAPTCHA Enterprise verifier using the
projects/{id}/assessmentsAPI. -
Bounded PSR-18 HTTP transport (
Model\Verification\Http\BoundedHttpClient)
with a configurable wall-clock budget; the verifier fails closed and fast. -
Per-provider client JS strategies (
view/base/web/js/provider/*) so any
provider/widget type renders from one form component. -
Provider-independent verified-cookie HMAC key (Magento crypt key) so switching
providers does not invalidate verified sessions. -
Data patch
Setup\Patch\Data\MigrateLegacyCaptchaConfigmigrating both the
legacyhryvinskyi_invisible_captcha/*(v2.x) andhryvinskyi_turnstile/*
settings into the unified v3 config tree. -
Configurable challenge accent color. New Challenge Page Appearance
sub-group under Route Protection
(hryvinskyi_invisible_captcha/route_protection/appearance/*) exposes
--primary(#2f6bd8),--primary-deep(#2557b6) and--primary-soft
(rgba(47,107,216,0.12)).ChallengeRendererinjects them as a CSP-safe
:rootoverride (values sanitized against CSS-injection) so the interstitial
re-themes per store view. -
Migration from Magento's native Google reCAPTCHA. New CLI command
hryvinskyi:invisible-captcha:migrate-recaptcha [--dry-run] [--force]imports
recaptcha_frontend/*/recaptcha_backend/*config (all scopes) into the
hryvinskyi_invisible_captcha/*tree: provider credentials (secrets copied
verbatim), per-formtype_for/*selectors, the v3 score threshold, and the
derived active provider + master switches. The migrator holds only mapping
policy (Model\Migration\RecaptchaMigrator) behind
Api\Migration\RecaptchaMigratorInterface, reads/writescore_config_data
through theApi\Migration\CoreConfigGatewayInterfaceport, carries per-run
state inModel\Migration\MigrationRun, and returns typed
Model\Migration\ChangeRecordresults; never clobbers existing values
without--force. -
Companion metapackage
hryvinskyi/magento2-invisible-captcha-recaptcha-replace
(published separately) thatreplaces everymagento/module-re-captcha-*
package to remove the native reCAPTCHA while leaving the rest of
magento/security-packageintact.
Changed
-
Unified, snake-case config tree under
hryvinskyi_invisible_captcha/{general, providers,form_protection,route_protection,advanced}. -
The form-verify flow is now provider-agnostic: one rich
Api\Verification\VerificationResultInterfacereplaces the reCAPTCHA-only
response; the "Provider" namespace now means a captcha vendor, while failure /
token / redirect strategies moved toModel\Strategy\*. -
Score thresholds apply only to score-based providers (v3 / Enterprise).
-
CSP whitelist is now global and covers Cloudflare + Google hosts across
script/frame/connect/form-action. -
Submit gating ("disable submit until ready") is now done with CSS + a small
parse-time inline script rendered next to the captcha marker, instead of
rewriting the whole HTML response server-side. The script adds the
hryvinskyi-captcha-disabled-submitclass to the surrounding form (the CSS in
_module.lessmakes the submit button unclickable while that class is
present);form-manager.jsremoves the class once the captcha is ready. -
Redesigned route-gate challenge page. The inline interstitial
(view/frontend/web/inline_challenge/) was reworked into a clean,
system-font layout and no longer loads Google Fonts (Inter / Cormorant
Garamond) — one fewer third-party request and a tighter CSP surface. The
placeholder contract andscript.jsbootstrap are unchanged.
Removed / BREAKING
Hryvinskyi_TurnstileProtectionis merged in and deleted. Its config under
hryvinskyi_turnstile/*is migrated automatically; external references to the
oldturnstile/verifyroute,X-Turnstile-Challengeheader,
hryvinskyi_turnstile_verifiedcookie andTURNSTILE_VERIFIEDvary key are
renamed (run a one-time full-page-cache flush after deploy).- Legacy v2.x classes (
Helper\Config\*,Model\ReCaptcha\*,Model\Verify\*,
Model\Provider\{Failure,TokenResponse}*,Observer\Captcha,LayoutSettings)
are removed; integrators should target the newApi\*contracts. - Requires
hryvinskyi/magento2-theme-assets(replaces the previous
asset-renderer dependency). - Removed the server-side
Plugin\DisableSubmitresponse rewriter and dropped
thevoku/simple_html_dom,symfony/dom-crawlerandext-domrequirements
it needed; submit gating is now handled in CSS + JS (see above).
[2.5.5] - 2026-06-01
Fixed
- PHP 8.5: added the
: intreturn type and an explicitreturn Command::SUCCESS;
toCommand\Captcha::execute(), matching Symfony ConsoleCommand::execute(): int
(fixes the "must be compatible" fatal error). - Monolog 3:
Model\Debug::write()now acceptsMonolog\LogRecordinstead ofarray
to matchMagento\Framework\Logger\Handler\Base::write().
2.5.4 - 2026-06-01
Added
- PHP 8.5 support (
~8.5added to thephprequirement).
Fixed
- PHP 8.4/8.5 compatibility: replaced implicit-nullable parameter declarations
(Type $param = null) with explicit nullable types (?Type $param = null)
across the failure providers, area config, and abstract config classes, removing
the "Implicitly marking parameter as nullable is deprecated" notices.
Security
- Widened the
symfony/dom-crawlerrequirement from>=2.7 <5.0to
^5.4.52 || ^6.4.40 || ^7.4.12 || ^8.0.12. The previous constraint could only
resolve to versions affected by CVE-2026-45071 (XXE / local file disclosure
inDomCrawler::addXmlContent()viavalidateOnParse = true), because no fix
exists for the 2.x–4.x branches. The new constraint pins to the patched releases
and aligns with the Symfony components shipped by Magento 2.4.x.
2.5.3 - 2025-10-06
Changed
- Updated dependencies and removed leftover
console.logdebug output.
2.5.2 - 2025-08-12
Changed
composer.jsonmaintenance.
2.5.1 - 2025-06-24
Changed
composer.jsonmaintenance.
2.5.0 - 2025-06-17
Changed
composer.jsonmaintenance.
| Version | Stability | QA Status | Compatibility | Released |
|---|---|---|---|---|
| 3.1.0 | stable | Fail | Magento 2.4.8-2.4.9 Details | 2026-07-11 08:35:50 |
| 3.0.4 | stable | Fail | Magento 2.4.8-2.4.9 Details | 2026-07-08 17:32:52 |
| 3.0.3 | stable | Not tested | Not yet tested Details | 2026-07-08 17:00:33 |
| 3.0.2 | stable | Not tested | Not yet tested Details | 2026-07-08 16:06:56 |
| 3.0.1 | stable | Not tested | Not yet tested Details | 2026-07-08 15:21:10 |
| 3.0.0 | stable | Not tested | Not yet tested Details | 2026-07-08 14:14:55 |
| 2.5.5 | stable | Fail | Magento 2.4.8 Details | 2026-06-01 19:51:18 |
| 2.5.4 | stable | Not tested | Not yet tested Details | 2026-06-01 12:25:56 |
| 2.5.3 | stable | Not tested | Not yet tested Details | 2025-11-05 18:06:24 |
| 2.5.2 | stable | Not tested | Not yet tested Details | 2025-08-12 09:01:06 |
| 2.5.1 | stable | Not tested | Not yet tested Details | 2025-06-24 09:54:36 |
| 2.5.0 | stable | Not tested | Not yet tested Details | 2025-06-16 22:29:28 |
| 2.4.10 | stable | Not tested | Not yet tested Details | 2025-06-16 22:27:23 |
| 2.4.9 | stable | Not tested | Not yet tested Details | 2025-05-05 18:55:28 |
| 2.4.8.1 | stable | Not tested | Not yet tested Details | 2025-05-05 13:18:08 |
| 2.4.8 | stable | Not tested | Not yet tested Details | 2024-03-28 20:23:25 |
| 2.4.7 | stable | Not tested | Not yet tested Details | 2024-01-18 12:29:05 |
| 2.4.6 | stable | Not tested | Not yet tested Details | 2024-01-18 11:31:05 |
| 2.4.5 | stable | Not tested | Not yet tested Details | 2023-12-13 10:56:58 |
| 2.4.5-dev | dev | Not tested | Not yet tested Details | 2022-09-01 07:46:00 |
| 2.4.4 | stable | Not tested | Not yet tested Details | 2022-06-23 09:59:18 |
| 2.4.3 | stable | Not tested | Not yet tested Details | 2022-06-21 12:26:03 |
| 2.4.2 | stable | Not tested | Not yet tested Details | 2022-03-22 10:43:26 |
| 2.4.1 | stable | Not tested | Not yet tested Details | 2022-03-22 10:34:54 |
| 2.4 | stable | Not tested | Not yet tested Details | 2022-03-22 10:29:50 |
| 2.3.2 | stable | Not tested | Not yet tested Details | 2021-06-19 08:57:54 |
| 2.3.1 | stable | Not tested | Not yet tested Details | 2020-09-23 22:33:58 |
| 2.3.0 | stable | Not tested | Not yet tested Details | 2020-09-07 14:03:20 |
| 2.2.7 | stable | Not tested | Not yet tested Details | 2020-08-13 19:20:15 |
| 2.2.6 | stable | Not tested | Not yet tested Details | 2020-08-07 20:19:11 |
| 2.2.5 | stable | Not tested | Not yet tested Details | 2020-08-03 19:09:47 |
| 2.2.4 | stable | Not tested | Not yet tested Details | 2020-06-16 20:43:55 |
| 2.2.3 | stable | Not tested | Not yet tested Details | 2020-06-16 20:35:41 |
| 2.2.2 | stable | Not tested | Not yet tested Details | 2020-06-16 19:32:58 |
| 2.2.1 | stable | Not tested | Not yet tested Details | 2020-06-16 17:29:41 |
| 2.2.0 | stable | Not tested | Not yet tested Details | 2020-06-16 11:40:32 |
| 2.1.10.1 | stable | Not tested | Not yet tested Details | 2020-06-09 13:23:05 |
| 2.1.10 | stable | Not tested | Not yet tested Details | 2020-05-21 17:55:11 |
| 2.1.9.1 | stable | Not tested | Not yet tested Details | 2020-02-12 21:05:09 |
| 2.1.9 | stable | Not tested | Not yet tested Details | 2020-02-09 20:23:41 |
| 2.1.8 | stable | Not tested | Not yet tested Details | 2020-02-05 13:28:44 |
| 2.1.7.1 | stable | Not tested | Not yet tested Details | 2020-01-30 09:31:19 |
| 2.1.7 | stable | Not tested | Not yet tested Details | 2020-01-10 13:06:04 |
| 2.1.6 | stable | Not tested | Not yet tested Details | 2019-10-20 10:46:16 |
| 2.1.5 | stable | Not tested | Not yet tested Details | 2019-09-25 13:33:54 |
| 2.1.4 | stable | Not tested | Not yet tested Details | 2019-09-24 20:37:24 |
| 2.1.3 | stable | Not tested | Not yet tested Details | 2019-09-17 12:16:12 |
| 2.1.2 | stable | Not tested | Not yet tested Details | 2019-09-17 11:19:58 |
| 2.1.1 | stable | Not tested | Not yet tested Details | 2019-09-17 07:22:08 |
| 2.1.0.4 | stable | Not tested | Not yet tested Details | 2019-06-11 14:19:19 |
| 2.1.0.3 | stable | Not tested | Not yet tested Details | 2019-06-11 13:38:50 |
| 2.1.0.2 | stable | Not tested | Not yet tested Details | 2019-06-05 13:36:06 |
| 2.1.0.1 | stable | Not tested | Not yet tested Details | 2019-06-01 17:21:40 |
| 2.1.0 | stable | Not tested | Not yet tested Details | 2019-05-26 10:36:24 |
| 2.0.4 | stable | Not tested | Not yet tested Details | 2019-04-22 14:56:14 |
| 2.0.3 | stable | Not tested | Not yet tested Details | 2019-04-18 15:26:56 |
| 2.0.2 | stable | Not tested | Not yet tested Details | 2019-04-09 12:02:45 |
| 2.0.1 | stable | Not tested | Not yet tested Details | 2019-03-22 08:50:42 |
| 2.0.0 | stable | Not tested | Not yet tested Details | 2019-03-21 23:52:56 |
| 1.0.1 | stable | Not tested | Not yet tested Details | 2018-06-27 12:07:03 |
| 1.0.0 | stable | Not tested | Not yet tested Details | 2017-11-10 09:37:22 |
Requires 29
| Package | Constraint |
|---|---|
| hryvinskyi/magento2-base | ^2.1 |
| hryvinskyi/magento2-configuration-fields | ^1.0.11 |
| hryvinskyi/magento2-theme-assets | ^1.0 |
| magento/framework | * |
| magento/module-backend | * |
| magento/module-catalog | * |
| magento/module-catalog-search | * |
| magento/module-checkout | * |
| magento/module-config | * |
| magento/module-contact | * |
| magento/module-customer | * |
| magento/module-graph-ql | * |
| magento/module-layered-navigation | * |
| magento/module-newsletter | * |
| magento/module-paypal | * |
| magento/module-quote | * |
| magento/module-review | * |
| magento/module-sales-rule | * |
| magento/module-send-friend | * |
| magento/module-store | * |
| magento/module-url-rewrite | * |
| magento/module-webapi | * |
| magento/module-wishlist | * |
| nyholm/psr7 | ^1.5 |
| php | ~8.1.0||~8.2.0||~8.3.0||~8.4.0||~8.5.0 |
| psr/http-client | ^1.0 |
| psr/http-factory | ^1.0 |
| psr/http-message | ^1.0 || ^2.0 |
| symfony/http-client | ^5 || ^6 || ^7 || ^8 |
Requires-dev 1
| Package | Constraint |
|---|---|
| roave/security-advisories | dev-latest |
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.
| Tool | Status | Findings | Summary |
|---|---|---|---|
| PHPCS | Fail | 270 | 13 errors, 257 warnings (ruleset: Magento2), 10 auto-fixable with phpcbf |
| PHPMD | Warning | 33 | 33 rule violations (MissingImport:12, TooManyPublicMethods:7, NPathComplexity:4, ErrorControlOperator:3, UnusedFormalParameter:3) |
| Cpd | Warning | 9 | 9 duplicated chunks spanning 305 total lines (min-lines=5, min-tokens=70) |
| Composer validate | Info | 21 | valid; 21 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.
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
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 hryvinskyi
View vendorTurn 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.