This documentation always refers to the latest published release.
Live demo: Try this and other plugins live, no installation, no risk: demo.markus-michalski.net
| Form | Mechanism | Notes |
|---|---|---|
| Customer registration | RegisterRouteDecorator decorates the core route RegisterRoute |
The check runs before the account is created, so no fake accounts appear. Active when the master switch and "Protect Registration Form" are on |
| Contact form | Subscriber on the event contact_form.send |
No switch of its own, protected as soon as the master switch is on |
| Newsletter registration | Subscriber on NewsletterRegisterEvent |
Own switch "Protect Newsletter Form" |
All three forms use the same widget integration and the same central check path (SpamProtectionFacade), see Technical Details.
The plugin extends Shopware's standard templates via sw_extends and adds the widget, the honeypot field and the form-token field to the form's captcha block:
{% sw_extends '@Storefront/storefront/component/account/register.html.twig' %}
{% block component_account_register_captcha %}
{{ parent() }}
{% if registration_protection_enabled(context.salesChannel.id) %}
{% if altcha_enabled(context.salesChannel.id) %}
<script src="{{ asset('bundles/mmdaltchaintegration/altcha/altcha.min.js') }}?v={{ altcha_asset_version() }}" type="module"></script>
<script src="{{ asset('bundles/mmdaltchaintegration/altcha/altcha-i18n-de.js') }}?v={{ altcha_asset_version() }}" type="module"></script>
<altcha-widget challenge="/altcha/challenge" name="altcha"></altcha-widget>
{% endif %}
{% if honeypot_enabled(context.salesChannel.id) %}
{{ honeypot_field(context.salesChannel.id)|raw }}
{% endif %}
{{ form_token_field(context.salesChannel.id)|raw }}
{% endif %}
{% endblock %}
Three blocks of the core templates are affected: component_account_register_captcha (registration), cms_form_contact_captcha (contact) and cms_form_newsletter_captcha (newsletter).
Custom theme: If your theme overrides one of these blocks itself, the override must call
{{ parent() }}so that widget, honeypot field and token field are still output. Withoutparent()the submission still runs through the server-side check chain, but the widget is missing in the browser. As long as ALTCHA is active, the shop then rejects it (reasonaltcha_missingin the log).
The widget comes from the plugin, not from a CDN:
bundles/mmdaltchaintegration/altcha/altcha.min.js
bundles/mmdaltchaintegration/altcha/altcha-i18n-de.js
The script URLs carry a version query (?v=...) so browsers do not load the old widget from cache after an update. The challenge path /altcha/challenge is hard-coded in the template.
For the widget texts the widget picks the language in this order: a configured language first, then the document language, then the browser languages as a fallback. The plugin registers only a German language pack. The widget can therefore show German texts even on an English page if the visitor's browser is set to German. For other languages the widget's default texts apply.
The error message below the checkbox is the Shopware snippet mmdAltcha.widget.error ("Verification failed" / "Überprüfung fehlgeschlagen") and can be changed under Settings > Shop > Snippets.
| Function | Returns | Description |
|---|---|---|
altcha_enabled(salesChannelId) |
bool |
ALTCHA active (master switch and widget switch) |
honeypot_enabled(salesChannelId) |
bool |
Honeypot active (master switch and honeypot switch) |
honeypot_field(salesChannelId) |
string (HTML) |
Outputs the hidden honeypot field |
form_token_field(salesChannelId) |
string (HTML) |
Outputs the signed token field for the minimum fill time, empty when the minimum time is 0 |
newsletter_protection_enabled(salesChannelId) |
bool |
Newsletter protection active |
registration_protection_enabled(salesChannelId) |
bool |
Registration protection active |
altcha_asset_version() |
string |
Version string for the script URLs |
In your own form you place the widget and fields with the same building blocks:
{% if altcha_enabled(context.salesChannel.id) %}
<script src="{{ asset('bundles/mmdaltchaintegration/altcha/altcha.min.js') }}?v={{ altcha_asset_version() }}" type="module"></script>
<altcha-widget challenge="/altcha/challenge" name="altcha"></altcha-widget>
{% endif %}
{% if honeypot_enabled(context.salesChannel.id) %}
{{ honeypot_field(context.salesChannel.id)|raw }}
{% endif %}
{{ form_token_field(context.salesChannel.id)|raw }}
The plugin does not attach the server-side check to new forms automatically. You call SpamProtectionFacade::validate() yourself, for example in a controller or event subscriber, with a SpamCheckRequest (IP, payload from the altcha field, form data, context, sales channel, action). This is not a stable extension contract: the classes are internal plugin classes.
| Rejection reason | Display |
|---|---|
disposable_email (disposable e-mail) |
Error message at the e-mail field through Shopware's form validation, snippet error.MMD_ALTCHA__DISPOSABLE_EMAIL |
| all other reasons (honeypot, too fast, gibberish, content validation, ALTCHA, rate limit) | The plugin throws a SpamDetectedException (HTTP 403, error code MMD_ALTCHA__SPAM_DETECTED). Shopware shows its general error page for it, not a message at the field |
Only the disposable e-mail is something the visitor can fix, so it gets a concrete message at the field. All other reasons stay vague so a bot does not learn which layer stopped it. You find the actual reason in the Shopware log, see Technical Details.