Age Verification for PrestaShop – Blocking Modal for CBD, Alcohol, Vape & Healthcare Pros
Complete documentation for the dfagegate module: installation, mode configuration (standard CBD/alcohol/vape/firearms and medical), category and product targeting, preview mode, multilingual customization, GDPR compliance, statistics and diagnostics.
The dfagegate module adds a blocking age-verification modal to your PrestaShop 8 or 9 store. It covers two distinct markets: standard mode for age-restricted products (CBD, alcohol, vape, firearms, lighters, 18+ products), and medical mode for medical devices restricted to healthcare professionals (in France, within the meaning of article L5122-9 of the Public Health Code).
Compatibility — PrestaShop 1.7.7+, 8.x and 9.0. PHP 7.4 minimum, 8.1+ recommended. Multistore and multilingual (FR/EN/ES/DE pre-filled at installation).
Installation
Installation follows the standard PrestaShop workflow. After purchasing on DataFirefly, you receive a ZIP file dfagegate-X.Y.Z.zip.
Via the back office (recommended)
- Log in to your PrestaShop back office
- Go to Modules → Module Catalog
- Click Upload a module at the top right
- Select the ZIP
dfagegate-X.Y.Z.zip - Once uploaded, click Install
Via FTP
- Unzip the archive locally
- Upload the
dfagegate/folder into your PrestaShopmodules/directory - Go to Modules → Module Catalog
- Search for “DataFirefly Age Gate” and click Install
Important — After installation, the module is disabled by default. This is intentional: it lets you configure texts and mode before blocking your store. Go to the module configuration and enable the toggle in the General tab once everything is set up.
Initial setup
Access the configuration: Modules → Installed modules → DataFirefly Age Gate → Configure.
Configuration is organized into 6 tabs:
- General — activation, mode, verification type, minimum age, display scope
- Content — multilingual texts (title, message, buttons, legal notices)
- Design — logo, colors, backdrop blur
- Behavior — cookie, redirect, bypass rules
- Medical mode — professions and license number (only applies in medical mode)
- Logs & GDPR — logging, statistics and diagnostics
General tab
- Enable module — Yes/No toggle, controls the global display of the modal
- Mode — Standard (CBD, alcohol, vape, firearms) or Medical (healthcare professionals)
- Verification type — Yes/no button, Date of birth, or Profession declaration
- Minimum age — 18 by default, 21 for some markets (US for alcohol, for example)
- Display scope — whole store, targeted categories or targeted products (see below)
- Test the modal — button that opens the store in preview mode (see the Diagnostics section)
Which verification type should you choose? The yes/no button suits most cases (CBD, mainstream alcohol, vape): fast, low friction, sufficient deterrent for most regulatory audits. Date of birth is stricter and recommended for firearms or nicotine liquids. Profession declaration is reserved for medical mode.
Display scope: targeting categories or products
Since version 1.1.0, the Display scope setting in the General tab offers three modes:
- Whole store — the modal shows on every page. This is the legacy behaviour and the default value
- Targeted categories only — the modal shows on the pages of the checked categories and on the product pages of products belonging to them. Selection happens in a category tree with checkboxes, an instant search filter and a selection counter
- Targeted products only — the modal only shows on the pages of the selected products. Search by name or reference, suggestions appear as you type, and each selected product becomes a removable chip
In categories mode, the Include sub-categories option (enabled by default) automatically covers the full descendance of the checked categories: check “CBD” and its sub-categories “Oils”, “Flowers” or “Resins” are covered without checking them one by one. The expansion relies on PrestaShop nested sets, in a single query.
Mixed store? This is the typical targeting use case: a fine grocery that also sells spirits, an online pharmacy with a professional equipment department. Target the regulated department by category: it is protected, and the rest of the store stays modal-free.
Products mode and listing pages — In targeted-products mode, listing pages (category, search, new products) do not trigger the modal: only the selected product pages do. To cover a whole department, listings included, prefer category targeting.
The display decision is made server-side, not in JavaScript. The diagnostic comment in the head tag shows the active scope: scope=all, scope=categories or scope=products. On save, a targeted scope with an empty selection is rejected with an error message, to avoid locking the configuration in an inconsistent state.
Content tab
Each language enabled in your PrestaShop has its own text block. Defaults are pre-filled in FR, EN, ES and DE. For each language you can configure:
- Title — displayed large at the top of the modal (default: “Age Verification”)
- Message — main explanatory text, line breaks are preserved
- Confirm button — positive button label (default: “I am 18 or older”)
- Refuse button — negative button label
- Legal notice — text at the bottom of the modal (e.g. “Please drink responsibly.”)
- Denial message — screen shown when the user refuses, before redirect
HTML — Content is sanitized on save (plain text only). Line breaks are converted to break tags at render time via an automatic nl2br filter.
Design tab
- Logo — PNG, JPG, SVG or WEBP, 2 MB max, displayed at the top of the modal
- Background color — main card color (white by default)
- Primary color — title and main button (black #111111 by default)
- Text color — message body
- Overlay color — veil behind the modal, accepts CSS
rgba()and hex formats (default:rgba(15,15,20,0.85)) - Backdrop blur — background blur (modern effect, works in all current browsers)
Behavior tab
- Cookie duration — in days (90 by default). Set
0for a session cookie (deleted when the browser closes) - Redirect URL on refusal — leave empty to only show the denial message, or set an external URL (Google, supplier page, etc.)
- Bypass IPs — list of IPs (one per line) for which the modal never shows. Ideal for you and your team during tests
- Bypass URLs — excluded partial paths. Pre-filled with
/legal,/contact, and other legal pages - Bypass logged-in customers — if enabled, already-authenticated customers never see the modal (useful if your store is restricted to already-validated accounts)
Medical mode tab
This tab only applies when mode is set to Medical and verification type to Profession declaration.
- Profession list — one per line. Defaults: Doctor, Pharmacist, Nurse, Physiotherapist, Dentist, Veterinarian, Other healthcare professional. Fully customizable
- Mandatory license number (RPPS/ADELI) — if enabled, a field appears in the modal. Regex validation accepting 9 to 11 digits. The number is not stored, it is only validated server-side
Legal compliance — Medical mode materializes a sworn declaration in the spirit of French article L5122-9 of the Public Health Code, which restricts advertising of certain medical devices to authorized healthcare professionals. This module does not replace a legal review of your catalog by a specialized lawyer. Consult your counsel to validate your configuration and check the equivalent rules in your jurisdiction.
Logs & GDPR tab
- Log refusals — records each refusal in the
ps_dfagegate_logtable with SHA-256 hashed IP (never plain), date, reason - Log retention — retention period in days (365 by default,
0for unlimited). Refusals older than this period are deleted automatically every time the configuration page opens
Since version 1.1.0, this tab also shows refusal statistics: total over the last 30 days, breakdown by reason, and the 15 latest refusals with date, reason, declared age and profession where applicable. A Purge all refusal logs button (with confirmation) empties the table for the current store.
This tab also shows a GDPR summary:
- Cookie set —
dfagegate_ok - Category — strictly necessary (legal access compliance)
- Data — value “1”, configurable duration, SameSite=Lax, Secure on HTTPS
Configuring for your market
Mainstream CBD store
- Mode: Standard
- Verification type: Yes/no button
- Minimum age: 18
- Cookie duration: 90 days (good balance between compliance and UX)
- Redirect URL: empty (denial message only)
Premium spirits / alcohol store
- Mode: Standard
- Verification type: Date of birth (stricter control for premium markets)
- Minimum age: 18 (or 21 for US markets)
- Redirect URL: any official information page
Vape / nicotine e-liquid store
- Mode: Standard
- Verification type: Date of birth (strongly recommended for nicotine)
- Minimum age: 18
- Log refusals: Yes (useful in case of a regulatory audit)
Firearms store
- Mode: Standard
- Verification type: Date of birth (mandatory)
- Minimum age: 18
- Bypass URLs: add
/regulations,/hunting-license - Log refusals: Yes
Fine grocery with a spirits department (mixed store)
- Mode: Standard
- Verification type: Yes/no button
- Display scope: Targeted categories, with the Spirits category checked and sub-categories included
- Result: the modal only shows on the spirits department and its product pages, the rest of the grocery stays friction-free
Medical equipment store (professional devices)
- Mode: Medical
- Verification type: Profession declaration
- Profession list: Doctor, Pharmacist, Physiotherapist, Osteopath (adapt to your catalog)
- Mandatory license number: Yes
- Bypass logged-in customers: Yes (if you already validate the profession at registration)
How date-of-birth verification works
Unlike a simple button, date-of-birth verification performs the calculation server-side, not in the browser:
- The visitor enters day, month and year in the modal
- JavaScript sends these values to the AJAX controller
DfagegateAjaxModuleFrontController - PHP validates the date with
checkdate()then computes the age viaDateTimeImmutable::diff() - If the age is under the configured threshold, the server returns a JSON response with
success=false, denied=trueand the error message - The modal shows the denial message and redirects after 2 seconds
- If the age meets the threshold, the
dfagegate_okcookie is set and the modal closes
Why server-side? A purely JavaScript check can be bypassed via DevTools in under 10 seconds. Server-side calculation guarantees that an underage user cannot access the site, even with technical knowledge. This is essential to withstand a regulatory audit.
Note that the date of birth is never stored — it is used for a single calculation and then forgotten. Only the binary validation (accepted / refused) is retained via the cookie.
How medical mode works
- The modal shows a dropdown of professions (configurable) and optionally a license number field
- A mandatory sworn declaration checkbox is present
- The AJAX controller validates that a profession is selected
- If the license number is mandatory, the server validates the format via a regular expression accepting 9 to 11 consecutive digits
- No storage: neither the profession nor the number is kept in the database — the
dfagegate_okcookie only materializes the successful passage
Design choice — The regulation requires materializing the declaration, not necessarily a real-time check against a national registry. Our approach is minimum viable compliance: we ask for the declaration, formally validate it, log the refusal if enabled, but collect no unnecessary personal data. If you need real-time registry verification, that is a separate custom development.
Multistore
The module is 100% multistore compatible. All settings (mode, verification type, multilingual texts, colors, bypass rules, display scope) are stored per shop context via id_shop_group and id_shop. This means a single PrestaShop can host:
- A CBD FR store in standard mode at 18 with French texts
- A vape UK store in standard mode at 18 with English texts
- A medical equipment DE store in medical mode with mandatory license number and German texts
To configure a specific sub-store:
- In the context selector at the top of the back office, pick the target sub-store
- Open the module configuration
- Edit the values — they will be persisted for that store only
Hooks are registered on all stores at installation time via Shop::getCompleteListOfShopsID(), avoiding the classic pitfall of a module that only runs on the current store.
Compatibility with custom themes
The module uses the standard displayBeforeBodyClosingTag hook to inject the modal just before the closing body tag. This hook is supposed to be universal on PrestaShop 1.7.5+.
Unfortunately, some custom themes do not call this hook in their layout. For this case, dfagegate ships with a JavaScript injection fallback:
- PHP pre-renders the full modal HTML and passes it to JS via
Media::addJsDef - On
DOMContentLoaded, the script checks whether the element with IDdfagegate-modalexists in the DOM - If yes, all good — the hook worked
- If not, the script injects the modal itself via
insertAdjacentHTML('beforeend', ...) - A
console.infomessage confirms the fallback activated: “[dfagegate] modal injected via JS fallback (theme does not trigger displayBeforeBodyClosingTag).”
Practical result — The module works on any PrestaShop 1.7.5+ theme, including incomplete custom themes, without touching the layout. You can deploy it without coordinating with your theme agency.
Diagnostics and debugging
Once enabled, dfagegate adds an HTML comment in the head tag of every front page, in the form “dfagegate v1.1.0 enabled=1 scope=all should_display=1”.
This comment is your first diagnostic checkpoint. View the source of a front page (Ctrl+U or Cmd+U) and search for “dfagegate”.
| Comment | Interpretation |
|---|---|
| No comment at all | The displayHeader hook is not registered — check the module is installed and active |
enabled=0 |
The “Enable module” toggle is off in the General tab |
enabled=1 should_display=0 |
A bypass is active (your IP is whitelisted, the current URL matches an excluded path, or you are a logged-in customer with the bypass enabled), or the current page is outside the targeted scope — check the scope= value |
enabled=1 should_display=1 |
Server-side, everything is fine. If the modal does not appear, check the browser console for a fallback or error message |
The modal still does not appear?
- Is the diagnostic comment present with
should_display=1? If not, fix the config - Open the store in private browsing. The
dfagegate_okcookie may have been set in a previous session - Check your IP against the bypasses in the Behavior tab
- With a targeted scope, check that the tested page actually belongs to the selected categories or products
- Open the browser console (F12). Look for
[dfagegate]messages - Check PrestaShop logs in Advanced Parameters → Logs, filter on “dfagegate”
- Clear the PrestaShop cache after every configuration change: Advanced Parameters → Performance → Clear cache
Testing with preview mode
Since version 1.1.0, the Test the modal button in the General tab opens the store in a new tab with the ?dfagegate_preview=1 parameter. In this mode:
- The modal shows even if the
dfagegate_okcookie is already set - Confirming sets no cookie: the test is repeatable at will, without private browsing or manual deletion
With a targeted scope, navigate to a covered page while keeping the parameter in the URL to see the modal.
Resetting the cookie in your browser
- Open DevTools (F12)
- Application tab (Chrome) or Storage (Firefox)
- Cookies section → your domain
- Delete the
dfagegate_okrow - Reload the page
GDPR compliance
The dfagegate_ok cookie
- Type — strictly necessary to comply with a legal access obligation
- Legal basis — exempt from prior consent (in France, under article 82 of the Data Protection Act per CNIL guidance; similar exemptions exist under the ePrivacy framework)
- Value — binary (
1= confirmed) - Duration — configurable (90 days by default), or session if set to 0
- Attributes —
SameSite=Lax, automaticSecureon HTTPS,Path=/
In practice — You do not need to add this cookie to your consent banner. It falls into the same category as the PrestaShop session cookie or the CSRF cookie: necessary for the legal operation of the site, therefore exempt.
Refusal logs
If you enable Log refusals, every refusal is recorded in the ps_dfagegate_log table with:
id_shop— sub-store concernedreason— refusal reason (user_refusedordob_under_age)age— declared age if applicableprofession— declared profession if applicableip_hash— SHA-256 of the IP, never the plain IPdate_add— timestamp
SHA-256 hashing makes the IP non-reversible while still allowing deduplication of attempts (the same IP always produces the same hash). This is the compromise recommended by data-protection authorities for access statistics.
Retention is automatic since 1.1.0: entries older than the configured period (365 days by default) are purged every time the configuration page opens. A time-limited retention is exactly what the GDPR minimisation principle expects from this kind of log. The statistics in the Logs tab (30-day total, breakdown by reason, latest refusals) are based on this same hashed data.
Verification data
- The date of birth transits via AJAX for the calculation but is never stored
- The license number is validated server-side then forgotten, never stored
- Only the binary validation is retained, via the cookie
Technical structure and integration
Hooks used
displayHeader— injects the diagnostic commentactionFrontControllerSetMedia— registers CSS and JS, passes the config and the pre-rendered modal HTML to JSdisplayBeforeBodyClosingTag— renders the modal server-side (JS fallback if absent from the theme)
AJAX endpoints
The AJAX controller responds at /module/dfagegate/ajax and accepts two actions:
action=confirm— with parameters depending on the verification type (nothing for yes/no, day/month/year for DOB, profession/license for medical)action=refuse— logs the refusal and returns the redirect URL
Responses are JSON. A confirmation returns success=true. An underage refusal returns success=false, denied=true and the appropriate error message. A validation error returns success=false with the message. A user refusal returns success=true and redirect_url containing the configured redirect URL.
Database schema
A single table is created: ps_dfagegate_log. It contains the columns id_log (auto-increment primary key), id_shop, reason (varchar 64), age (nullable), profession (varchar 128 nullable), ip_hash (char 64 for the SHA-256) and date_add (datetime). Two secondary indexes optimize reporting queries: idx_shop_date on (id_shop, date_add) and idx_reason on reason.
Uninstallation
Disable without removing
Modules → Installed modules → DataFirefly Age Gate → Disable. Configuration and the log table are kept. You can re-enable at any time without reconfiguring.
Full uninstall
Modules → Installed modules → DataFirefly Age Gate → Uninstall. This action:
- Drops the
ps_dfagegate_logtable - Removes all configuration entries (23 scalar keys + 6 multilingual keys)
- Unregisters the hooks
- Removes the module from the system
Warning — Uninstallation is irreversible. If you want to keep the refusal log history (e.g. for a data-protection audit), export the table first.
Support and updates
Every license includes:
- 12 months of updates — PrestaShop compatibility, fixes, improvements
- Email technical support — response within 24 business hours, FR/EN
- 30-day refund
- Unencrypted source code — you are free to adapt the module to your specific needs
For any technical question or bug report, contact us from your DataFirefly account. Please include your PrestaShop version, PHP version, module version (visible at the top of the configuration screen), and if possible the diagnostic comment present in the head tag of your store.